├── .editorconfig ├── .github ├── release.yml └── workflows │ ├── main.yml │ ├── pull_request.yml │ ├── pull_request_label.yml │ └── soundness.yml ├── .gitignore ├── .license_header_template ├── .licenseignore ├── .spi.yml ├── .swift-format ├── .swiftformatignore ├── .unacceptablelanguageignore ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── IntegrationTests └── PluginTests │ └── Resources │ ├── Config │ ├── import-directory-1-grpc-swift-proto-generator-config.json │ ├── internal-grpc-swift-proto-generator-config.json │ └── public-grpc-swift-proto-generator-config.json │ ├── Protos │ ├── Foo │ │ ├── foo-messages.proto │ │ └── foo-service.proto │ ├── HelloWorld │ │ ├── HelloWorld.proto │ │ ├── Messages.proto │ │ └── Service.proto │ ├── noop │ │ └── noop.proto │ └── noop2 │ │ └── noop.proto │ └── Sources │ ├── FooHelloWorldAdopter.swift │ ├── HelloWorldAdopter.swift │ ├── NoOp.swift │ └── Package.swift ├── LICENSE ├── NOTICES.txt ├── Package.swift ├── Plugins ├── GRPCProtobufGenerator │ ├── BuildPluginConfig.swift │ ├── BuildPluginError.swift │ ├── Plugin.swift │ └── PluginsShared ├── GRPCProtobufGeneratorCommand │ ├── CommandConfig.swift │ ├── CommandPluginError.swift │ ├── Plugin.swift │ └── PluginsShared └── PluginsShared │ ├── GenerationConfig.swift │ └── PluginUtils.swift ├── README.md ├── SECURITY.md ├── Sources ├── CGRPCProtobuf │ ├── CGRPCProtobuf.c │ └── include │ │ └── CGRPCProtobuf.h ├── GRPCProtobuf │ ├── Coding.swift │ ├── ContiguousBytesAdapter.swift │ ├── Documentation.docc │ │ ├── Articles │ │ │ ├── API-stability-of-generated-code.md │ │ │ ├── Generating-stubs.md │ │ │ ├── Installing-protoc.md │ │ │ ├── Public-services-with-private-implementations.md │ │ │ └── Understanding-the-generated-code.md │ │ └── Documentation.md │ └── Errors │ │ ├── ErrorDetails+AnyPacking.swift │ │ ├── ErrorDetails+CustomStringConvertible.swift │ │ ├── ErrorDetails+Types.swift │ │ ├── ErrorDetails.swift │ │ ├── Generated │ │ ├── code.pb.swift │ │ ├── error_details.pb.swift │ │ └── status.pb.swift │ │ ├── GoogleRPCStatus.swift │ │ └── RPCError+GoogleRPCStatus.swift ├── GRPCProtobufCodeGen │ ├── CamelCaser.swift │ ├── ProtobufCodeGenParser.swift │ └── ProtobufCodeGenerator.swift └── protoc-gen-grpc-swift-2 │ ├── GenerateGRPC.swift │ ├── Options.swift │ └── Version.swift ├── Tests ├── GRPCProtobufCodeGenTests │ ├── CamelCaserTests.swift │ ├── Generated │ │ ├── bar-service.pb │ │ ├── foo-messages.pb │ │ ├── foo-service.pb │ │ ├── test-service.pb │ │ └── wkt-service.pb │ ├── ProtobufCodeGenParserTests.swift │ ├── ProtobufCodeGeneratorTests.swift │ └── Utilities.swift └── GRPCProtobufTests │ ├── Errors │ ├── DetailedErrorTests.swift │ └── Generated │ │ ├── error-service.grpc.swift │ │ └── error-service.pb.swift │ └── ProtobufCodingTests.swift └── dev ├── check-generated-code.sh ├── execute-plugin-tests.sh ├── format.sh ├── git.commit.template ├── license-check.sh ├── plugin-tests.sh ├── protos ├── fetch.sh ├── generate.sh ├── local │ ├── bar-service.proto │ ├── error-service.proto │ ├── foo-messages.proto │ ├── foo-service.proto │ ├── test-service.proto │ └── wkt-service.proto └── upstream │ └── google │ └── rpc │ ├── code.proto │ ├── error_details.proto │ └── status.proto ├── setup-plugin-tests.sh ├── soundness.sh └── version-bump.commit.template /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copyright 2024, gRPC Authors All rights reserved. 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 | root = true 16 | 17 | [*] 18 | end_of_line = lf 19 | trim_trailing_whitespace = true 20 | indent_style = space 21 | indent_size = 2 22 | 23 | [*.py] 24 | indent_size = 4 25 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: SemVer Major 4 | labels: 5 | - ⚠️ semver/major 6 | - title: SemVer Minor 7 | labels: 8 | - 🆕 semver/minor 9 | - title: SemVer Patch 10 | labels: 11 | - 🔨 semver/patch 12 | - title: Other Changes 13 | labels: 14 | - semver/none 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | schedule: 7 | - cron: "0 8,20 * * *" 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit tests 12 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 13 | with: 14 | linux_5_9_enabled: false 15 | linux_5_10_enabled: false 16 | linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" 17 | linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" 18 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" 19 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" 20 | 21 | construct-plugin-tests-matrix: 22 | name: Construct plugin tests matrix 23 | runs-on: ubuntu-latest 24 | outputs: 25 | plugin-tests-matrix: '${{ steps.generate-matrix.outputs.plugin-tests-matrix }}' 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | persist-credentials: false 31 | - id: generate-matrix 32 | run: echo "plugin-tests-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT" 33 | env: 34 | MATRIX_LINUX_5_9_ENABLED: false 35 | MATRIX_LINUX_5_10_ENABLED: false 36 | MATRIX_LINUX_COMMAND: "./dev/plugin-tests.sh" 37 | MATRIX_LINUX_SETUP_COMMAND: "apt-get update -y -q && apt-get install -y -q curl protobuf-compiler" 38 | 39 | plugin-tests-matrix: 40 | name: Plugin tests 41 | needs: construct-plugin-tests-matrix 42 | uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main 43 | with: 44 | name: "Plugin tests" 45 | matrix_string: '${{ needs.construct-plugin-tests-matrix.outputs.plugin-tests-matrix }}' 46 | 47 | static-sdk: 48 | name: Static SDK 49 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 50 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | types: [opened, reopened, synchronize] 7 | 8 | jobs: 9 | soundness: 10 | name: Soundness 11 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 12 | with: 13 | license_header_check_project_name: "gRPC" 14 | # This is done by a similar job defined in soundness.yml. It needs to be 15 | # separate in order to export an environment variable. 16 | api_breakage_check_enabled: false 17 | 18 | grpc-soundness: 19 | name: Soundness 20 | uses: ./.github/workflows/soundness.yml 21 | 22 | unit-tests: 23 | name: Unit tests 24 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 25 | with: 26 | linux_5_9_enabled: false 27 | linux_5_10_enabled: false 28 | linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" 29 | linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" 30 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" 31 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" 32 | 33 | construct-plugin-tests-matrix: 34 | name: Construct plugin tests matrix 35 | runs-on: ubuntu-latest 36 | outputs: 37 | plugin-tests-matrix: '${{ steps.generate-matrix.outputs.plugin-tests-matrix }}' 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | with: 42 | persist-credentials: false 43 | - id: generate-matrix 44 | run: echo "plugin-tests-matrix=$(curl -s https://raw.githubusercontent.com/apple/swift-nio/main/scripts/generate_matrix.sh | bash)" >> "$GITHUB_OUTPUT" 45 | env: 46 | MATRIX_LINUX_5_9_ENABLED: false 47 | MATRIX_LINUX_5_10_ENABLED: false 48 | MATRIX_LINUX_COMMAND: "./dev/plugin-tests.sh" 49 | MATRIX_LINUX_SETUP_COMMAND: "apt-get update -y -q && apt-get install -y -q curl protobuf-compiler" 50 | 51 | plugin-tests-matrix: 52 | name: Plugin tests 53 | needs: construct-plugin-tests-matrix 54 | uses: apple/swift-nio/.github/workflows/swift_test_matrix.yml@main 55 | with: 56 | name: "Plugin tests" 57 | matrix_string: '${{ needs.construct-plugin-tests-matrix.outputs.plugin-tests-matrix }}' 58 | 59 | cxx-interop: 60 | name: Cxx interop 61 | uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main 62 | with: 63 | linux_5_9_enabled: false 64 | linux_5_10_enabled: false 65 | 66 | static-sdk: 67 | name: Static SDK 68 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 69 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, reopened, synchronize] 6 | 7 | jobs: 8 | semver-label-check: 9 | name: Semantic version label check 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 1 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | persist-credentials: false 17 | - name: Check for Semantic Version label 18 | uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main 19 | -------------------------------------------------------------------------------- /.github/workflows/soundness.yml: -------------------------------------------------------------------------------- 1 | name: Soundness 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | swift-license-check: 8 | name: Swift license headers check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | with: 14 | persist-credentials: false 15 | - name: Mark the workspace as safe 16 | run: git config --global --add safe.directory ${GITHUB_WORKSPACE} 17 | - name: Run license check 18 | run: | 19 | ./dev/license-check.sh 20 | 21 | check-generated-code: 22 | name: Check generated code 23 | runs-on: ubuntu-latest 24 | container: 25 | image: swift:latest 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | persist-credentials: false 31 | - name: Mark the workspace as safe 32 | run: git config --global --add safe.directory ${GITHUB_WORKSPACE} 33 | - name: Install protoc 34 | run: apt update && apt install -y protobuf-compiler 35 | - name: Check generated code 36 | run: | 37 | ./dev/check-generated-code.sh 38 | 39 | api-breakage-check: 40 | name: API breakage check 41 | runs-on: ubuntu-latest 42 | container: 43 | image: swift:latest 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v4 47 | with: 48 | persist-credentials: false 49 | fetch-depth: 0 # Fetching tags requires fetch-depth: 0 (https://github.com/actions/checkout/issues/1471) 50 | - name: Mark the workspace as safe 51 | # https://github.com/actions/checkout/issues/766 52 | run: git config --global --add safe.directory ${GITHUB_WORKSPACE} 53 | - name: Run API breakage check 54 | shell: bash 55 | # See package.swift for why we set GRPC_SWIFT_PROTOBUF_NO_VERSION=1 56 | run: | 57 | export GRPC_SWIFT_PROTOBUF_NO_VERSION=1 58 | 59 | git fetch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} ${GITHUB_BASE_REF}:pull-base-ref 60 | BASELINE_REF='pull-base-ref' 61 | echo "Using baseline: $BASELINE_REF" 62 | swift package diagnose-api-breaking-changes "$BASELINE_REF" 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | project.xcworkspace 3 | xcuserdata 4 | DerivedData/ 5 | .build 6 | .swiftpm 7 | build 8 | /protoc-gen-swift 9 | /protoc-gen-grpc-swift 10 | third_party/** 11 | /Echo 12 | /EchoNIO 13 | /test.out 14 | /echo.pid 15 | /*.xcodeproj 16 | /scripts/tmp/ 17 | Examples/EchoWeb/dist 18 | Examples/EchoWeb/node_modules 19 | Examples/EchoWeb/package-lock.json 20 | dev/codegen-tests/**/generated/* 21 | /scripts/.swiftformat-source/ 22 | /scripts/.swift-format-source/ 23 | Package.resolved 24 | *.out.* 25 | /Performance/Benchmarks/.benchmarkBaselines/ -------------------------------------------------------------------------------- /.license_header_template: -------------------------------------------------------------------------------- 1 | @@ Copyright YEARS, gRPC Authors All rights reserved. 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 | -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | **/.gitignore 3 | .licenseignore 4 | .gitattributes 5 | .git-blame-ignore-revs 6 | .mailfilter 7 | .mailmap 8 | .spi.yml 9 | .swift-format 10 | .swiftformatignore 11 | .editorconfig 12 | .github/* 13 | *.md 14 | *.txt 15 | *.yml 16 | *.yaml 17 | *.json 18 | Package.swift 19 | **/Package.swift 20 | Package@-*.swift 21 | **/Package@-*.swift 22 | Package.resolved 23 | **/Package.resolved 24 | Makefile 25 | *.modulemap 26 | **/*.modulemap 27 | **/*.docc/* 28 | *.xcprivacy 29 | **/*.xcprivacy 30 | *.symlink 31 | **/*.symlink 32 | Dockerfile 33 | **/Dockerfile 34 | Snippets/* 35 | dev/git.commit.template 36 | dev/version-bump.commit.template 37 | dev/protos/local/* 38 | dev/protos/upstream/* 39 | IntegrationTests/PluginTests/**/*.proto 40 | .unacceptablelanguageignore 41 | LICENSE 42 | **/*.swift 43 | *.pb 44 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [GRPCProtobuf] 5 | swift_version: 6.0 6 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "fileScopedDeclarationPrivacy" : { 3 | "accessLevel" : "private" 4 | }, 5 | "indentation" : { 6 | "spaces" : 2 7 | }, 8 | "indentConditionalCompilationBlocks" : false, 9 | "indentSwitchCaseLabels" : false, 10 | "lineBreakAroundMultilineExpressionChainComponents" : false, 11 | "lineBreakBeforeControlFlowKeywords" : false, 12 | "lineBreakBeforeEachArgument" : true, 13 | "lineBreakBeforeEachGenericRequirement" : false, 14 | "lineLength" : 100, 15 | "maximumBlankLines" : 1, 16 | "prioritizeKeepingFunctionOutputTogether" : true, 17 | "respectsExistingLineBreaks" : true, 18 | "rules" : { 19 | "AllPublicDeclarationsHaveDocumentation" : false, 20 | "AlwaysUseLowerCamelCase" : false, 21 | "AmbiguousTrailingClosureOverload" : true, 22 | "BeginDocumentationCommentWithOneLineSummary" : false, 23 | "DoNotUseSemicolons" : true, 24 | "DontRepeatTypeInStaticProperties" : true, 25 | "FileScopedDeclarationPrivacy" : true, 26 | "FullyIndirectEnum" : true, 27 | "GroupNumericLiterals" : true, 28 | "IdentifiersMustBeASCII" : true, 29 | "NeverForceUnwrap" : false, 30 | "NeverUseForceTry" : false, 31 | "NeverUseImplicitlyUnwrappedOptionals" : false, 32 | "NoAccessLevelOnExtensionDeclaration" : true, 33 | "NoAssignmentInExpressions" : true, 34 | "NoBlockComments" : false, 35 | "NoCasesWithOnlyFallthrough" : true, 36 | "NoEmptyTrailingClosureParentheses" : true, 37 | "NoLabelsInCasePatterns" : false, 38 | "NoLeadingUnderscores" : false, 39 | "NoParensAroundConditions" : true, 40 | "NoVoidReturnOnFunctionSignature" : true, 41 | "OneCasePerLine" : true, 42 | "OneVariableDeclarationPerLine" : true, 43 | "OnlyOneTrailingClosureArgument" : true, 44 | "OrderedImports" : true, 45 | "ReturnVoidInsteadOfEmptyTuple" : true, 46 | "UseEarlyExits" : false, 47 | "UseLetInEveryBoundCaseVariable" : false, 48 | "UseShorthandTypeNames" : true, 49 | "UseSingleLinePropertyGetter" : false, 50 | "UseSynthesizedInitializer" : false, 51 | "UseTripleSlashForDocumentationComments" : true, 52 | "UseWhereClausesInForLoops" : false, 53 | "ValidateDocumentationComments" : false 54 | }, 55 | "spacesAroundRangeFormationOperators" : true, 56 | "tabWidth" : 2, 57 | "version" : 1 58 | } 59 | -------------------------------------------------------------------------------- /.swiftformatignore: -------------------------------------------------------------------------------- 1 | *.grpc.swift 2 | *.pb.swift 3 | -------------------------------------------------------------------------------- /.unacceptablelanguageignore: -------------------------------------------------------------------------------- 1 | **/*.pb.swift 2 | **/*.grpc.swift 3 | dev/protos/upstream/**/*.proto 4 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Community Code of Conduct 2 | 3 | gRPC follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please refer to the contributing guide in the 4 | [`grpc/grpc-swift`](https://github.com/grpc/grpc-swift) repository. 5 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/main/governance.md). 2 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Config/import-directory-1-grpc-swift-proto-generator-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "generatedSource": { 3 | "accessLevel": "internal" 4 | }, 5 | "protoc": { 6 | "importPaths": [ 7 | ".", 8 | "../directory_1" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Config/internal-grpc-swift-proto-generator-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "generatedSource": { 3 | "accessLevel": "internal" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Config/public-grpc-swift-proto-generator-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "generatedSource": { 3 | "accessLevel": "public" 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/Foo/foo-messages.proto: -------------------------------------------------------------------------------- 1 | // Leading trivia. 2 | syntax = "proto3"; 3 | 4 | package foo; 5 | 6 | message FooInput {} 7 | message FooOutput {} 8 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/Foo/foo-service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "Foo/foo-messages.proto"; 4 | 5 | package foo; 6 | 7 | service FooService1 { 8 | rpc Foo (FooInput) returns (FooOutput) {} 9 | } 10 | 11 | service FooService2 { 12 | rpc Foo (FooInput) returns (FooOutput) {} 13 | } 14 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/HelloWorld/HelloWorld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015, gRPC Authors All rights reserved. 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 | syntax = "proto3"; 15 | 16 | option java_multiple_files = true; 17 | option java_package = "io.grpc.examples.helloworld"; 18 | option java_outer_classname = "HelloWorldProto"; 19 | option objc_class_prefix = "HLW"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } 38 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/HelloWorld/Messages.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015, gRPC Authors All rights reserved. 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 | syntax = "proto3"; 15 | 16 | option java_multiple_files = true; 17 | option java_package = "io.grpc.examples.helloworld"; 18 | option java_outer_classname = "HelloWorldProto"; 19 | option objc_class_prefix = "HLW"; 20 | 21 | package helloworld; 22 | 23 | // The request message containing the user's name. 24 | message HelloRequest { 25 | string name = 1; 26 | } 27 | 28 | // The response message containing the greetings 29 | message HelloReply { 30 | string message = 1; 31 | } 32 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/HelloWorld/Service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015, gRPC Authors All rights reserved. 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 | syntax = "proto3"; 15 | 16 | option java_multiple_files = true; 17 | option java_package = "io.grpc.examples.helloworld"; 18 | option java_outer_classname = "HelloWorldProto"; 19 | option objc_class_prefix = "HLW"; 20 | 21 | package helloworld; 22 | 23 | import "Messages.proto"; 24 | 25 | // The greeting service definition. 26 | service Greeter { 27 | // Sends a greeting 28 | rpc SayHello (HelloRequest) returns (HelloReply) {} 29 | } 30 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/noop/noop.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/empty.proto"; 4 | 5 | package noop; 6 | 7 | service NoOpService { 8 | rpc NoOp(google.protobuf.Empty) returns (google.protobuf.Empty); 9 | } 10 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Protos/noop2/noop.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/empty.proto"; 4 | 5 | package noop2; 6 | 7 | service NoOpService { 8 | rpc NoOp(google.protobuf.Empty) returns (google.protobuf.Empty); 9 | } 10 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Sources/FooHelloWorldAdopter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCCore 18 | import GRPCInProcessTransport 19 | import GRPCProtobuf 20 | 21 | @main 22 | struct PluginAdopter { 23 | static func main() async throws { 24 | let inProcess = InProcessTransport() 25 | try await withGRPCServer(transport: inProcess.server, services: [Greeter()]) { server in 26 | try await withGRPCClient(transport: inProcess.client) { client in 27 | try await Self.doRPC(Helloworld_Greeter.Client(wrapping: client)) 28 | } 29 | } 30 | 31 | try await withGRPCServer(transport: inProcess.server, services: [FooService1()]) { server in 32 | try await withGRPCClient(transport: inProcess.client) { client in 33 | try await Self.doRPC(Foo_FooService1.Client(wrapping: client)) 34 | } 35 | } 36 | } 37 | 38 | static func doRPC(_ greeter: Helloworld_Greeter.Client) async throws { 39 | do { 40 | let reply = try await greeter.sayHello(.with { $0.name = "(ignored)" }) 41 | print("Reply: \(reply.message)") 42 | } catch { 43 | print("Error: \(error)") 44 | } 45 | } 46 | 47 | static func doRPC(_ fooService1: Foo_FooService1.Client) async throws { 48 | do { 49 | let reply = try await fooService1.foo(.with { _ in () }) 50 | print("Reply: \(reply.hashValue)") 51 | } catch { 52 | print("Error: \(error)") 53 | } 54 | } 55 | } 56 | 57 | struct Greeter: Helloworld_Greeter.SimpleServiceProtocol { 58 | func sayHello( 59 | request: Helloworld_HelloRequest, 60 | context: ServerContext 61 | ) async throws -> Helloworld_HelloReply { 62 | return .with { reply in 63 | reply.message = "Hello, world!" 64 | } 65 | } 66 | } 67 | 68 | struct FooService1: Foo_FooService1.SimpleServiceProtocol { 69 | func foo(request: Foo_FooInput, context: GRPCCore.ServerContext) async throws -> Foo_FooOutput { 70 | return .with { _ in 71 | () 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Sources/HelloWorldAdopter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCCore 18 | import GRPCInProcessTransport 19 | import GRPCProtobuf 20 | 21 | @main 22 | struct PluginAdopter { 23 | static func main() async throws { 24 | let inProcess = InProcessTransport() 25 | try await withGRPCServer(transport: inProcess.server, services: [Greeter()]) { server in 26 | try await withGRPCClient(transport: inProcess.client) { client in 27 | try await Self.doRPC(Helloworld_Greeter.Client(wrapping: client)) 28 | } 29 | } 30 | } 31 | 32 | static func doRPC(_ greeter: Helloworld_Greeter.Client) async throws { 33 | do { 34 | let reply = try await greeter.sayHello(.with { $0.name = "(ignored)" }) 35 | print("Reply: \(reply.message)") 36 | } catch { 37 | print("Error: \(error)") 38 | } 39 | } 40 | } 41 | 42 | struct Greeter: Helloworld_Greeter.SimpleServiceProtocol { 43 | func sayHello( 44 | request: Helloworld_HelloRequest, 45 | context: ServerContext 46 | ) async throws -> Helloworld_HelloReply { 47 | return .with { reply in 48 | reply.message = "Hello, world!" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Sources/NoOp.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025, gRPC Authors All rights reserved. 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 | 17 | import GRPCCore 18 | import GRPCProtobuf 19 | import SwiftProtobuf 20 | 21 | @main 22 | struct PluginAdopter { 23 | static func main() async throws { 24 | } 25 | } 26 | 27 | struct NoOp: Noop_NoOpService.SimpleServiceProtocol { 28 | func noOp( 29 | request: Google_Protobuf_Empty, 30 | context: ServerContext 31 | ) async throws -> Google_Protobuf_Empty { 32 | return Google_Protobuf_Empty() 33 | } 34 | } 35 | 36 | struct NoOp2: Noop2_NoOpService.SimpleServiceProtocol { 37 | func noOp( 38 | request: Google_Protobuf_Empty, 39 | context: ServerContext 40 | ) async throws -> Google_Protobuf_Empty { 41 | return Google_Protobuf_Empty() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /IntegrationTests/PluginTests/Resources/Sources/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | /* 3 | * Copyright 2024, gRPC Authors All rights reserved. 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 | * http://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 | 18 | import PackageDescription 19 | 20 | let package = Package( 21 | name: "grpc-adopter", 22 | platforms: [ 23 | .macOS(.v15), 24 | .iOS(.v18), 25 | .tvOS(.v18), 26 | .watchOS(.v11), 27 | .visionOS(.v2), 28 | ], 29 | dependencies: [ 30 | // Dependency on grpc-swift-protobuf to be added by setup-plugin-tests.sh script 31 | .package( 32 | url: "https://github.com/grpc/grpc-swift-2.git", 33 | from: "2.0.0" 34 | ) 35 | ], 36 | targets: [ 37 | .executableTarget( 38 | name: "grpc-adopter", 39 | dependencies: [ 40 | .product(name: "GRPCCore", package: "grpc-swift-2"), 41 | .product(name: "GRPCInProcessTransport", package: "grpc-swift-2"), 42 | .product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"), 43 | ], 44 | plugins: [ 45 | .plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf") 46 | ] 47 | ) 48 | ] 49 | ) 50 | -------------------------------------------------------------------------------- /NOTICES.txt: -------------------------------------------------------------------------------- 1 | The gRPC Swift Project 2 | ====================== 3 | 4 | Copyright 2021 The gRPC Swift Project 5 | 6 | The gRPC Swift project licenses this file to you under the Apache License, 7 | version 2.0 (the "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at: 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | License for the specific language governing permissions and limitations 16 | under the License. 17 | 18 | ------------------------------------------------------------------------------- 19 | 20 | This product uses scripts derived from SwiftNIO's integration testing 21 | framework: 'test_01_allocation_counts.sh', 'run-nio-alloc-counter-tests.sh' and 22 | 'test_functions.sh'. 23 | 24 | It also uses derivations of SwiftNIO's lock 'NIOLock.swift' and locked value box 25 | 'NIOLockedValueBox.swift'. 26 | 27 | * LICENSE (Apache License 2.0): 28 | * https://github.com/apple/swift-nio/blob/main/LICENSE.txt 29 | * HOMEPAGE: 30 | * https://github.com/apple/swift-nio 31 | 32 | --- 33 | 34 | This product uses derivations of SwiftNIOHTTP2's implementation of case 35 | insensitive comparison of strings, found in 'HPACKHeader.swift'. 36 | 37 | * LICENSE (Apache License 2.0): 38 | * https://github.com/apple/swift-nio-http2/blob/main/LICENSE.txt 39 | * HOMEPAGE: 40 | * https://github.com/apple/swift-nio-http2 41 | 42 | --- 43 | 44 | This product contains a derivation of the backpressure aware async stream from 45 | the Swift project. 46 | 47 | * LICENSE (Apache License 2.0): 48 | * https://github.com/apple/swift/blob/main/LICENSE.txt 49 | * HOMEPAGE: 50 | * https://github.com/apple/swift 51 | 52 | --- 53 | 54 | This product uses derivations of swift-extras/swift-extras-base64 'Base64.swift'. 55 | 56 | * LICENSE (Apache License 2.0): 57 | * https://github.com/swift-extras/swift-extras-base64/blob/main/LICENSE 58 | * HOMEPAGE: 59 | * https://github.com/swift-extras/swift-extras-base64 60 | 61 | --- 62 | 63 | This product uses derivations of apple/swift-openapi-generator 'StructuredSwiftRepresentation.swift', 64 | 'TypeName.swift', 'TypeUsage.swift', 'Builtins.swift', 'RendererProtocol.swift', 'TextBasedProtocol', 65 | 'Test_TextBasedRenderer', and 'SnippetBasedReferenceTests.swift'. 66 | 67 | * LICENSE (Apache License 2.0): 68 | * https://github.com/apple/swift-openapi-generator/blob/main/LICENSE.txt 69 | * HOMEPAGE: 70 | * https://github.com/apple/swift-openapi-generator 71 | 72 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | /* 3 | * Copyright 2024, gRPC Authors All rights reserved. 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 | * http://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 | 18 | import PackageDescription 19 | 20 | let products: [Product] = [ 21 | .library( 22 | name: "GRPCProtobuf", 23 | targets: ["GRPCProtobuf"] 24 | ), 25 | .executable( 26 | name: "protoc-gen-grpc-swift-2", 27 | targets: ["protoc-gen-grpc-swift-2"] 28 | ), 29 | .plugin( 30 | name: "GRPCProtobufGenerator", 31 | targets: ["GRPCProtobufGenerator"] 32 | ), 33 | .plugin( 34 | name: "generate-grpc-code-from-protos", 35 | targets: ["generate-grpc-code-from-protos"] 36 | ), 37 | ] 38 | 39 | let dependencies: [Package.Dependency] = [ 40 | .package( 41 | url: "https://github.com/grpc/grpc-swift-2.git", 42 | from: "2.0.0" 43 | ), 44 | .package( 45 | url: "https://github.com/apple/swift-protobuf.git", 46 | from: "1.28.1" 47 | ), 48 | ] 49 | 50 | // ------------------------------------------------------------------------------------------------- 51 | 52 | // This adds some build settings which allow us to map "@available(gRPCSwiftProtobuf 2.x, *)" to 53 | // the appropriate OS platforms. 54 | let nextMinorVersion = 1 55 | let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in 56 | let name = "gRPCSwiftProtobuf" 57 | let version = "2.\(minor)" 58 | let platforms = "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0" 59 | let setting = "AvailabilityMacro=\(name) \(version):\(platforms)" 60 | return .enableExperimentalFeature(setting) 61 | } 62 | 63 | let defaultSwiftSettings: [SwiftSetting] = 64 | availabilitySettings + [ 65 | .swiftLanguageMode(.v6), 66 | .enableUpcomingFeature("ExistentialAny"), 67 | .enableUpcomingFeature("InternalImportsByDefault"), 68 | .enableUpcomingFeature("MemberImportVisibility"), 69 | ] 70 | 71 | // ------------------------------------------------------------------------------------------------- 72 | 73 | var targets: [Target] = [ 74 | // protoc plugin for grpc-swift-2 75 | .executableTarget( 76 | name: "protoc-gen-grpc-swift-2", 77 | dependencies: [ 78 | .target(name: "GRPCProtobufCodeGen"), 79 | .product(name: "GRPCCodeGen", package: "grpc-swift-2"), 80 | .product(name: "SwiftProtobuf", package: "swift-protobuf"), 81 | .product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"), 82 | ], 83 | swiftSettings: defaultSwiftSettings 84 | ), 85 | 86 | // Runtime serialization components 87 | .target( 88 | name: "GRPCProtobuf", 89 | dependencies: [ 90 | .product(name: "GRPCCore", package: "grpc-swift-2"), 91 | .product(name: "SwiftProtobuf", package: "swift-protobuf"), 92 | ], 93 | swiftSettings: defaultSwiftSettings 94 | ), 95 | .testTarget( 96 | name: "GRPCProtobufTests", 97 | dependencies: [ 98 | .target(name: "GRPCProtobuf"), 99 | .product(name: "GRPCCore", package: "grpc-swift-2"), 100 | .product(name: "GRPCInProcessTransport", package: "grpc-swift-2"), 101 | .product(name: "SwiftProtobuf", package: "swift-protobuf"), 102 | ], 103 | swiftSettings: defaultSwiftSettings 104 | ), 105 | 106 | // Code generator library for protoc-gen-grpc-swift-2 107 | .target( 108 | name: "GRPCProtobufCodeGen", 109 | dependencies: [ 110 | .product(name: "GRPCCodeGen", package: "grpc-swift-2"), 111 | .product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"), 112 | ], 113 | swiftSettings: defaultSwiftSettings 114 | ), 115 | .testTarget( 116 | name: "GRPCProtobufCodeGenTests", 117 | dependencies: [ 118 | .target(name: "GRPCProtobufCodeGen"), 119 | .product(name: "GRPCCodeGen", package: "grpc-swift-2"), 120 | .product(name: "SwiftProtobuf", package: "swift-protobuf"), 121 | .product(name: "SwiftProtobufPluginLibrary", package: "swift-protobuf"), 122 | ], 123 | resources: [ 124 | .copy("Generated") 125 | ], 126 | swiftSettings: defaultSwiftSettings 127 | ), 128 | 129 | // Code generator build plugin 130 | .plugin( 131 | name: "GRPCProtobufGenerator", 132 | capability: .buildTool(), 133 | dependencies: [ 134 | .target(name: "protoc-gen-grpc-swift-2"), 135 | .product(name: "protoc-gen-swift", package: "swift-protobuf"), 136 | ] 137 | ), 138 | 139 | // Code generator SwiftPM command 140 | .plugin( 141 | name: "generate-grpc-code-from-protos", 142 | capability: .command( 143 | intent: .custom( 144 | verb: "generate-grpc-code-from-protos", 145 | description: "Generate Swift code for gRPC services from protobuf definitions." 146 | ), 147 | permissions: [ 148 | .writeToPackageDirectory( 149 | reason: 150 | "To write the generated Swift files back into the source directory of the package." 151 | ) 152 | ] 153 | ), 154 | dependencies: [ 155 | .target(name: "protoc-gen-grpc-swift-2"), 156 | .product(name: "protoc-gen-swift", package: "swift-protobuf"), 157 | ], 158 | path: "Plugins/GRPCProtobufGeneratorCommand" 159 | ), 160 | ] 161 | 162 | // ------------------------------------------------------------------------------------------------- 163 | 164 | extension Context { 165 | fileprivate static var versionString: String { 166 | guard let git = Self.gitInformation else { return "" } 167 | 168 | if let tag = git.currentTag { 169 | return tag 170 | } else { 171 | let suffix = git.hasUncommittedChanges ? " (modified)" : "" 172 | return git.currentCommit + suffix 173 | } 174 | } 175 | 176 | fileprivate static var buildCGRPCProtobuf: Bool { 177 | let noVersion = Context.environment.keys.contains("GRPC_SWIFT_PROTOBUF_NO_VERSION") 178 | return !noVersion 179 | } 180 | } 181 | 182 | // Having a C module as a transitive dependency of a plugin seems to trip up the API breakage 183 | // checking tool. See also https://github.com/swiftlang/swift-package-manager/issues/8081 184 | // 185 | // The CGRPCProtobuf module (which only includes package version information) is conditionally 186 | // compiled and included based on an environment variable. This is set in CI only for the API 187 | // breakage checking job to avoid tripping up SwiftPM. 188 | if Context.buildCGRPCProtobuf { 189 | targets.append( 190 | .target( 191 | name: "CGRPCProtobuf", 192 | cSettings: [ 193 | .define("CGRPC_GRPC_SWIFT_PROTOBUF_VERSION", to: "\"\(Context.versionString)\"") 194 | ] 195 | ) 196 | ) 197 | 198 | for target in targets { 199 | if target.name == "protoc-gen-grpc-swift-2" { 200 | target.dependencies.append(.target(name: "CGRPCProtobuf")) 201 | } 202 | } 203 | } 204 | 205 | // ------------------------------------------------------------------------------------------------- 206 | 207 | let package = Package( 208 | name: "grpc-swift-protobuf", 209 | products: products, 210 | dependencies: dependencies, 211 | targets: targets 212 | ) 213 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGenerator/BuildPluginConfig.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import Foundation 18 | 19 | /// The config of the build plugin. 20 | struct BuildPluginConfig: Codable { 21 | /// Config defining which components should be considered when generating source. 22 | struct Generate { 23 | /// Whether server code is generated. 24 | /// 25 | /// Defaults to `true`. 26 | var servers: Bool 27 | /// Whether client code is generated. 28 | /// 29 | /// Defaults to `true`. 30 | var clients: Bool 31 | /// Whether message code is generated. 32 | /// 33 | /// Defaults to `true`. 34 | var messages: Bool 35 | 36 | static let defaults = Self( 37 | servers: true, 38 | clients: true, 39 | messages: true 40 | ) 41 | 42 | private init(servers: Bool, clients: Bool, messages: Bool) { 43 | self.servers = servers 44 | self.clients = clients 45 | self.messages = messages 46 | } 47 | } 48 | 49 | /// Config relating to the generated code itself. 50 | struct GeneratedSource { 51 | /// The visibility of the generated files. 52 | /// 53 | /// Defaults to `Internal`. 54 | var accessLevel: GenerationConfig.AccessLevel 55 | /// Whether imports should have explicit access levels. 56 | /// 57 | /// Defaults to `false`. 58 | var accessLevelOnImports: Bool 59 | 60 | static let defaults = Self( 61 | accessLevel: .internal, 62 | accessLevelOnImports: false 63 | ) 64 | 65 | private init(accessLevel: GenerationConfig.AccessLevel, accessLevelOnImports: Bool) { 66 | self.accessLevel = accessLevel 67 | self.accessLevelOnImports = accessLevelOnImports 68 | } 69 | } 70 | 71 | /// Config relating to the protoc invocation. 72 | struct Protoc { 73 | /// Specify the directory in which to search for imports. 74 | /// 75 | /// Paths are relative to the location of the specifying config file. 76 | /// Build plugins only have access to files within the target's source directory. 77 | /// May be specified multiple times; directories will be searched in order. 78 | /// The target source directory is always appended 79 | /// to the import paths. 80 | var importPaths: [String] 81 | 82 | /// The path to the `protoc` executable binary. 83 | /// 84 | /// If this is not set, Swift Package Manager will try to find the tool itself. 85 | var executablePath: String? 86 | 87 | static let defaults = Self( 88 | importPaths: [], 89 | executablePath: nil 90 | ) 91 | 92 | private init(importPaths: [String], executablePath: String?) { 93 | self.importPaths = importPaths 94 | self.executablePath = executablePath 95 | } 96 | } 97 | 98 | /// Config defining which components should be considered when generating source. 99 | var generate: Generate 100 | /// Config relating to the nature of the generated code. 101 | var generatedSource: GeneratedSource 102 | /// Config relating to the protoc invocation. 103 | var protoc: Protoc 104 | 105 | static let defaults = Self( 106 | generate: Generate.defaults, 107 | generatedSource: GeneratedSource.defaults, 108 | protoc: Protoc.defaults 109 | ) 110 | private init(generate: Generate, generatedSource: GeneratedSource, protoc: Protoc) { 111 | self.generate = generate 112 | self.generatedSource = generatedSource 113 | self.protoc = protoc 114 | } 115 | 116 | // Codable conformance with defaults 117 | enum CodingKeys: String, CodingKey { 118 | case generate 119 | case generatedSource 120 | case protoc 121 | } 122 | 123 | init(from decoder: any Decoder) throws { 124 | let container = try decoder.container(keyedBy: CodingKeys.self) 125 | 126 | self.generate = 127 | try container.decodeIfPresent(Generate.self, forKey: .generate) ?? Self.defaults.generate 128 | self.generatedSource = 129 | try container.decodeIfPresent(GeneratedSource.self, forKey: .generatedSource) 130 | ?? Self.defaults.generatedSource 131 | self.protoc = 132 | try container.decodeIfPresent(Protoc.self, forKey: .protoc) ?? Self.defaults.protoc 133 | } 134 | } 135 | 136 | extension BuildPluginConfig.Generate: Codable { 137 | // Codable conformance with defaults 138 | enum CodingKeys: String, CodingKey { 139 | case servers 140 | case clients 141 | case messages 142 | } 143 | 144 | init(from decoder: any Decoder) throws { 145 | let container = try decoder.container(keyedBy: CodingKeys.self) 146 | 147 | self.servers = 148 | try container.decodeIfPresent(Bool.self, forKey: .servers) ?? Self.defaults.servers 149 | self.clients = 150 | try container.decodeIfPresent(Bool.self, forKey: .clients) ?? Self.defaults.clients 151 | self.messages = 152 | try container.decodeIfPresent(Bool.self, forKey: .messages) ?? Self.defaults.messages 153 | } 154 | } 155 | 156 | extension BuildPluginConfig.GeneratedSource: Codable { 157 | // Codable conformance with defaults 158 | enum CodingKeys: String, CodingKey { 159 | case accessLevel 160 | case accessLevelOnImports 161 | } 162 | 163 | init(from decoder: any Decoder) throws { 164 | let container = try decoder.container(keyedBy: CodingKeys.self) 165 | 166 | self.accessLevel = 167 | try container.decodeIfPresent(GenerationConfig.AccessLevel.self, forKey: .accessLevel) 168 | ?? Self.defaults.accessLevel 169 | self.accessLevelOnImports = 170 | try container.decodeIfPresent(Bool.self, forKey: .accessLevelOnImports) 171 | ?? Self.defaults.accessLevelOnImports 172 | } 173 | } 174 | 175 | extension BuildPluginConfig.Protoc: Codable { 176 | // Codable conformance with defaults 177 | enum CodingKeys: String, CodingKey { 178 | case importPaths 179 | case executablePath 180 | } 181 | 182 | init(from decoder: any Decoder) throws { 183 | let container = try decoder.container(keyedBy: CodingKeys.self) 184 | 185 | self.importPaths = 186 | try container.decodeIfPresent([String].self, forKey: .importPaths) 187 | ?? Self.defaults.importPaths 188 | self.executablePath = try container.decodeIfPresent(String.self, forKey: .executablePath) 189 | } 190 | } 191 | 192 | extension GenerationConfig { 193 | init(buildPluginConfig: BuildPluginConfig, configFilePath: URL, outputPath: URL) { 194 | self.servers = buildPluginConfig.generate.servers 195 | self.clients = buildPluginConfig.generate.clients 196 | self.messages = buildPluginConfig.generate.messages 197 | // Use path to underscores as it ensures output files are unique (files generated from 198 | // "foo/bar.proto" won't collide with those generated from "bar/bar.proto" as they'll be 199 | // uniquely named "foo_bar.(grpc|pb).swift" and "bar_bar.(grpc|pb).swift". 200 | self.fileNaming = .pathToUnderscores 201 | self.accessLevel = buildPluginConfig.generatedSource.accessLevel 202 | self.accessLevelOnImports = buildPluginConfig.generatedSource.accessLevelOnImports 203 | // Generate absolute paths for the imports relative to the config file in which they are specified 204 | self.importPaths = buildPluginConfig.protoc.importPaths.map { relativePath in 205 | configFilePath.deletingLastPathComponent().absoluteStringNoScheme + "/" + relativePath 206 | } 207 | self.protocPath = buildPluginConfig.protoc.executablePath 208 | self.outputPath = outputPath.absoluteStringNoScheme 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGenerator/BuildPluginError.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025, gRPC Authors All rights reserved. 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 | 17 | enum BuildPluginError: Error { 18 | case incompatibleTarget(String) 19 | case noConfigFilesFound 20 | } 21 | 22 | extension BuildPluginError: CustomStringConvertible { 23 | var description: String { 24 | switch self { 25 | case .incompatibleTarget(let target): 26 | "Build plugin applied to incompatible target (\(target))." 27 | case .noConfigFilesFound: 28 | "No config files found. The build plugin relies on the existence of one or more '\(configFileName)' files in the target source." 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGenerator/PluginsShared: -------------------------------------------------------------------------------- 1 | ../PluginsShared -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGeneratorCommand/CommandConfig.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import Foundation 18 | import PackagePlugin 19 | 20 | struct CommandConfig { 21 | var common: GenerationConfig 22 | 23 | var verbose: Bool 24 | var dryRun: Bool 25 | 26 | static let defaults = Self( 27 | common: .init( 28 | accessLevel: .internal, 29 | servers: true, 30 | clients: true, 31 | messages: true, 32 | fileNaming: .fullPath, 33 | accessLevelOnImports: false, 34 | importPaths: [], 35 | outputPath: "" 36 | ), 37 | verbose: false, 38 | dryRun: false 39 | ) 40 | 41 | static let parameterGroupSeparator = "--" 42 | } 43 | 44 | extension CommandConfig { 45 | static func parse( 46 | argumentExtractor argExtractor: inout ArgumentExtractor, 47 | pluginWorkDirectory: URL 48 | ) throws -> CommandConfig { 49 | var config = CommandConfig.defaults 50 | 51 | for flag in OptionsAndFlags.allCases { 52 | switch flag { 53 | case .accessLevel: 54 | if let value = argExtractor.extractSingleOption(named: flag.rawValue) { 55 | if let accessLevel = GenerationConfig.AccessLevel(rawValue: value) { 56 | config.common.accessLevel = accessLevel 57 | } else { 58 | throw CommandPluginError.unknownAccessLevel(value) 59 | } 60 | } 61 | 62 | case .noServers: 63 | // Handled by `.servers` 64 | continue 65 | case .servers: 66 | let servers = argExtractor.extractFlag(named: OptionsAndFlags.servers.rawValue) 67 | let noServers = argExtractor.extractFlag(named: OptionsAndFlags.noServers.rawValue) 68 | if servers > 0 && noServers > 0 { 69 | throw CommandPluginError.conflictingFlags( 70 | OptionsAndFlags.servers.rawValue, 71 | OptionsAndFlags.noServers.rawValue 72 | ) 73 | } else if servers > 0 { 74 | config.common.servers = true 75 | } else if noServers > 0 { 76 | config.common.servers = false 77 | } 78 | 79 | case .noClients: 80 | // Handled by `.clients` 81 | continue 82 | case .clients: 83 | let clients = argExtractor.extractFlag(named: OptionsAndFlags.clients.rawValue) 84 | let noClients = argExtractor.extractFlag(named: OptionsAndFlags.noClients.rawValue) 85 | if clients > 0 && noClients > 0 { 86 | throw CommandPluginError.conflictingFlags( 87 | OptionsAndFlags.clients.rawValue, 88 | OptionsAndFlags.noClients.rawValue 89 | ) 90 | } else if clients > 0 { 91 | config.common.clients = true 92 | } else if noClients > 0 { 93 | config.common.clients = false 94 | } 95 | 96 | case .noMessages: 97 | // Handled by `.messages` 98 | continue 99 | case .messages: 100 | let messages = argExtractor.extractFlag(named: OptionsAndFlags.messages.rawValue) 101 | let noMessages = argExtractor.extractFlag(named: OptionsAndFlags.noMessages.rawValue) 102 | if messages > 0 && noMessages > 0 { 103 | throw CommandPluginError.conflictingFlags( 104 | OptionsAndFlags.messages.rawValue, 105 | OptionsAndFlags.noMessages.rawValue 106 | ) 107 | } else if messages > 0 { 108 | config.common.messages = true 109 | } else if noMessages > 0 { 110 | config.common.messages = false 111 | } 112 | 113 | case .fileNaming: 114 | if let value = argExtractor.extractSingleOption(named: flag.rawValue) { 115 | if let fileNaming = GenerationConfig.FileNaming(rawValue: value) { 116 | config.common.fileNaming = fileNaming 117 | } else { 118 | throw CommandPluginError.unknownFileNamingStrategy(value) 119 | } 120 | } 121 | 122 | case .accessLevelOnImports: 123 | if argExtractor.extractFlag(named: flag.rawValue) > 0 { 124 | config.common.accessLevelOnImports = true 125 | } 126 | 127 | case .importPath: 128 | config.common.importPaths = argExtractor.extractOption(named: flag.rawValue) 129 | 130 | case .protocPath: 131 | config.common.protocPath = argExtractor.extractSingleOption(named: flag.rawValue) 132 | 133 | case .outputPath: 134 | config.common.outputPath = 135 | argExtractor.extractSingleOption(named: flag.rawValue) 136 | ?? pluginWorkDirectory.absoluteStringNoScheme 137 | 138 | case .verbose: 139 | let verbose = argExtractor.extractFlag(named: flag.rawValue) 140 | config.verbose = verbose != 0 141 | 142 | case .dryRun: 143 | let dryRun = argExtractor.extractFlag(named: flag.rawValue) 144 | config.dryRun = dryRun != 0 145 | 146 | case .help: 147 | () // handled elsewhere 148 | } 149 | } 150 | 151 | if let argument = argExtractor.remainingArguments.first { 152 | throw CommandPluginError.unknownOption(argument) 153 | } 154 | 155 | return config 156 | } 157 | } 158 | 159 | extension ArgumentExtractor { 160 | mutating func extractSingleOption(named optionName: String) -> String? { 161 | let values = self.extractOption(named: optionName) 162 | if values.count > 1 { 163 | Diagnostics.warning( 164 | "'--\(optionName)' was unexpectedly repeated, the first value will be used." 165 | ) 166 | } 167 | return values.first 168 | } 169 | } 170 | 171 | /// All valid input options/flags 172 | enum OptionsAndFlags: String, CaseIterable { 173 | case servers 174 | case noServers = "no-servers" 175 | case clients 176 | case noClients = "no-clients" 177 | case messages 178 | case noMessages = "no-messages" 179 | case fileNaming = "file-naming" 180 | case accessLevel = "access-level" 181 | case accessLevelOnImports = "access-level-on-imports" 182 | case importPath = "import-path" 183 | case protocPath = "protoc-path" 184 | case outputPath = "output-path" 185 | case verbose 186 | case dryRun = "dry-run" 187 | 188 | case help 189 | } 190 | 191 | extension OptionsAndFlags { 192 | func usageDescription() -> String { 193 | switch self { 194 | case .servers: 195 | return "Generate server code. Generated by default." 196 | case .noServers: 197 | return "Do not generate server code. Generated by default." 198 | case .clients: 199 | return "Generate client code. Generated by default." 200 | case .noClients: 201 | return "Do not generate client code. Generated by default." 202 | case .messages: 203 | return "Generate message code. Generated by default." 204 | case .noMessages: 205 | return "Do not generate message code. Generated by default." 206 | case .fileNaming: 207 | return 208 | "The naming scheme for output files [fullPath/pathToUnderscores/dropPath]. Defaults to fullPath." 209 | case .accessLevel: 210 | return 211 | "The access level of the generated source [internal/public/package]. Defaults to internal." 212 | case .accessLevelOnImports: 213 | return "Whether imports should have explicit access levels. Defaults to false." 214 | case .importPath: 215 | return 216 | "The directory in which to search for imports. May be specified multiple times. If none are specified the current working directory is used." 217 | case .protocPath: 218 | return "The path to the protoc binary." 219 | case .dryRun: 220 | return "Print but do not execute the protoc commands." 221 | case .outputPath: 222 | return "The directory into which the generated source files are created." 223 | case .verbose: 224 | return "Emit verbose output." 225 | case .help: 226 | return "Print this help." 227 | } 228 | } 229 | 230 | static func printHelp(requested: Bool) { 231 | let printMessage: (String) -> Void 232 | if requested { 233 | printMessage = { message in print(message) } 234 | } else { 235 | printMessage = Stderr.print 236 | } 237 | 238 | printMessage( 239 | "Usage: swift package generate-grpc-code-from-protos [flags] [\(CommandConfig.parameterGroupSeparator)] [input files]" 240 | ) 241 | printMessage("") 242 | printMessage("Flags:") 243 | printMessage("") 244 | 245 | let spacing = 3 246 | let maxLength = 247 | (OptionsAndFlags.allCases.map(\.rawValue).max(by: { $0.count < $1.count })?.count ?? 0) 248 | + spacing 249 | for flag in OptionsAndFlags.allCases { 250 | printMessage( 251 | " --\(flag.rawValue.padding(toLength: maxLength, withPad: " ", startingAt: 0))\(flag.usageDescription())" 252 | ) 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGeneratorCommand/CommandPluginError.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025, gRPC Authors All rights reserved. 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 | 17 | enum CommandPluginError: Error { 18 | case invalidArgumentValue(name: String, value: String) 19 | case missingInputFile 20 | case unknownOption(String) 21 | case unknownAccessLevel(String) 22 | case unknownFileNamingStrategy(String) 23 | case conflictingFlags(String, String) 24 | case generationFailure( 25 | errorDescription: String, 26 | executable: String, 27 | arguments: [String], 28 | stdErr: String? 29 | ) 30 | case tooManyParameterSeparators 31 | } 32 | 33 | extension CommandPluginError: CustomStringConvertible { 34 | var description: String { 35 | switch self { 36 | case .invalidArgumentValue(let name, let value): 37 | return "Invalid value '\(value)', for '\(name)'." 38 | case .missingInputFile: 39 | return "No input file(s) specified." 40 | case .unknownOption(let name): 41 | return "Provided option is unknown: \(name)." 42 | case .unknownAccessLevel(let value): 43 | return "Provided access level is unknown: \(value)." 44 | case .unknownFileNamingStrategy(let value): 45 | return "Provided file naming strategy is unknown: \(value)." 46 | case .conflictingFlags(let flag1, let flag2): 47 | return "Provided flags conflict: '\(flag1)' and '\(flag2)'." 48 | case .generationFailure(let errorDescription, let executable, let arguments, let stdErr): 49 | var message = """ 50 | Code generation failed with: \(errorDescription). 51 | \tExecutable: \(executable) 52 | \tArguments: \(arguments.joined(separator: " ")) 53 | """ 54 | if let stdErr { 55 | message += """ 56 | \n\tprotoc error output: 57 | \t\(stdErr) 58 | """ 59 | } 60 | return message 61 | case .tooManyParameterSeparators: 62 | return "Unexpected parameter structure, too many '--' separators." 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGeneratorCommand/Plugin.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import Foundation 18 | import PackagePlugin 19 | 20 | extension GRPCProtobufGeneratorCommandPlugin: CommandPlugin { 21 | func performCommand(context: PluginContext, arguments: [String]) async throws { 22 | try self.performCommand( 23 | arguments: arguments, 24 | tool: context.tool, 25 | pluginWorkDirectoryURL: context.pluginWorkDirectoryURL 26 | ) 27 | } 28 | } 29 | 30 | #if canImport(XcodeProjectPlugin) 31 | import XcodeProjectPlugin 32 | 33 | // Entry-point when using Xcode projects 34 | extension GRPCProtobufGeneratorCommandPlugin: XcodeCommandPlugin { 35 | func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { 36 | try self.performCommand( 37 | arguments: arguments, 38 | tool: context.tool, 39 | pluginWorkDirectoryURL: context.pluginWorkDirectoryURL 40 | ) 41 | } 42 | } 43 | #endif 44 | 45 | @main 46 | struct GRPCProtobufGeneratorCommandPlugin { 47 | /// Command plugin common code 48 | func performCommand( 49 | arguments: [String], 50 | tool: (String) throws -> PluginContext.Tool, 51 | pluginWorkDirectoryURL: URL 52 | ) throws { 53 | let flagsAndOptions: [String] 54 | let inputFiles: [String] 55 | 56 | let separatorCount = arguments.count { $0 == CommandConfig.parameterGroupSeparator } 57 | switch separatorCount { 58 | case 0: 59 | var argExtractor = ArgumentExtractor(arguments) 60 | // check if help requested 61 | if argExtractor.extractFlag(named: OptionsAndFlags.help.rawValue) > 0 { 62 | OptionsAndFlags.printHelp(requested: true) 63 | return 64 | } 65 | 66 | inputFiles = arguments 67 | flagsAndOptions = [] 68 | 69 | case 1: 70 | let splitIndex = arguments.firstIndex(of: CommandConfig.parameterGroupSeparator)! 71 | flagsAndOptions = Array(arguments[.. 0 { 81 | OptionsAndFlags.printHelp(requested: true) 82 | return 83 | } 84 | 85 | // MARK: Configuration 86 | let commandConfig: CommandConfig 87 | do { 88 | commandConfig = try CommandConfig.parse( 89 | argumentExtractor: &argExtractor, 90 | pluginWorkDirectory: pluginWorkDirectoryURL 91 | ) 92 | } catch { 93 | throw error 94 | } 95 | 96 | if commandConfig.verbose { 97 | Stderr.print("InputFiles: \(inputFiles.joined(separator: ", "))") 98 | } 99 | 100 | let config = commandConfig.common 101 | let protocPath = try deriveProtocPath(using: config, tool: tool) 102 | let protocGenGRPCSwiftPath = try tool("protoc-gen-grpc-swift-2").url 103 | let protocGenSwiftPath = try tool("protoc-gen-swift").url 104 | 105 | let outputDirectory = URL(fileURLWithPath: config.outputPath) 106 | if commandConfig.verbose { 107 | Stderr.print( 108 | "Generated files will be written to: '\(outputDirectory.absoluteStringNoScheme)'" 109 | ) 110 | } 111 | 112 | let inputFileURLs = inputFiles.map { URL(fileURLWithPath: $0) } 113 | 114 | // MARK: protoc-gen-grpc-swift-2 115 | if config.clients || config.servers { 116 | let arguments = constructProtocGenGRPCSwiftArguments( 117 | config: config, 118 | fileNaming: config.fileNaming, 119 | inputFiles: inputFileURLs, 120 | protoDirectoryPaths: config.importPaths, 121 | protocGenGRPCSwiftPath: protocGenGRPCSwiftPath, 122 | outputDirectory: outputDirectory 123 | ) 124 | 125 | try executeProtocInvocation( 126 | executableURL: protocPath, 127 | arguments: arguments, 128 | verbose: commandConfig.verbose, 129 | dryRun: commandConfig.dryRun 130 | ) 131 | 132 | if !commandConfig.dryRun, commandConfig.verbose { 133 | Stderr.print("Generated gRPC Swift files for \(inputFiles.joined(separator: ", ")).") 134 | } 135 | } 136 | 137 | // MARK: protoc-gen-swift 138 | if config.messages { 139 | let arguments = constructProtocGenSwiftArguments( 140 | config: config, 141 | fileNaming: config.fileNaming, 142 | inputFiles: inputFileURLs, 143 | protoDirectoryPaths: config.importPaths, 144 | protocGenSwiftPath: protocGenSwiftPath, 145 | outputDirectory: outputDirectory 146 | ) 147 | 148 | try executeProtocInvocation( 149 | executableURL: protocPath, 150 | arguments: arguments, 151 | verbose: commandConfig.verbose, 152 | dryRun: commandConfig.dryRun 153 | ) 154 | 155 | if !commandConfig.dryRun, commandConfig.verbose { 156 | Stderr.print( 157 | "Generated protobuf message Swift files for \(inputFiles.joined(separator: ", "))." 158 | ) 159 | } 160 | } 161 | } 162 | } 163 | 164 | /// Execute a single invocation of `protoc`, printing output and if in verbose mode the invocation 165 | /// - Parameters: 166 | /// - executableURL: The path to the `protoc` executable. 167 | /// - arguments: The arguments to be passed to `protoc`. 168 | /// - verbose: Whether or not to print verbose output 169 | /// - dryRun: If this invocation is a dry-run, i.e. will not actually be executed 170 | func executeProtocInvocation( 171 | executableURL: URL, 172 | arguments: [String], 173 | verbose: Bool, 174 | dryRun: Bool 175 | ) throws { 176 | if verbose { 177 | Stderr.print("\(executableURL.absoluteStringNoScheme) \\") 178 | Stderr.print(" \(arguments.joined(separator: " \\\n "))") 179 | } 180 | 181 | if dryRun { 182 | return 183 | } 184 | 185 | let process = Process() 186 | process.executableURL = executableURL 187 | process.arguments = arguments 188 | 189 | let outputPipe = Pipe() 190 | let errorPipe = Pipe() 191 | process.standardOutput = outputPipe 192 | process.standardError = errorPipe 193 | 194 | do { 195 | try process.run() 196 | } catch { 197 | try printProtocOutput(outputPipe, verbose: verbose) 198 | let stdErr: String? 199 | if let errorData = try errorPipe.fileHandleForReading.readToEnd() { 200 | stdErr = String(decoding: errorData, as: UTF8.self) 201 | } else { 202 | stdErr = nil 203 | } 204 | throw CommandPluginError.generationFailure( 205 | errorDescription: "\(error)", 206 | executable: executableURL.absoluteStringNoScheme, 207 | arguments: arguments, 208 | stdErr: stdErr 209 | ) 210 | } 211 | process.waitUntilExit() 212 | 213 | try printProtocOutput(outputPipe, verbose: verbose) 214 | 215 | if process.terminationReason == .exit && process.terminationStatus == 0 { 216 | return 217 | } 218 | 219 | let stdErr: String? 220 | if let errorData = try errorPipe.fileHandleForReading.readToEnd() { 221 | stdErr = String(decoding: errorData, as: UTF8.self) 222 | } else { 223 | stdErr = nil 224 | } 225 | let problem = "\(process.terminationReason):\(process.terminationStatus)" 226 | throw CommandPluginError.generationFailure( 227 | errorDescription: problem, 228 | executable: executableURL.absoluteStringNoScheme, 229 | arguments: arguments, 230 | stdErr: stdErr 231 | ) 232 | } 233 | 234 | func printProtocOutput(_ stdOut: Pipe, verbose: Bool) throws { 235 | if verbose, let outputData = try stdOut.fileHandleForReading.readToEnd() { 236 | let output = String(decoding: outputData, as: UTF8.self) 237 | let lines = output.split { $0.isNewline } 238 | print("protoc output:") 239 | for line in lines { 240 | print("\t\(line)") 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Plugins/GRPCProtobufGeneratorCommand/PluginsShared: -------------------------------------------------------------------------------- 1 | ../PluginsShared -------------------------------------------------------------------------------- /Plugins/PluginsShared/GenerationConfig.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | /// The config used when generating code whether called from the build or command plugin. 18 | struct GenerationConfig { 19 | /// The access level (i.e. visibility) of the generated files. 20 | enum AccessLevel: String { 21 | /// The generated files should have `internal` access level. 22 | case `internal` = "Internal" 23 | /// The generated files should have `public` access level. 24 | case `public` = "Public" 25 | /// The generated files should have `package` access level. 26 | case `package` = "Package" 27 | } 28 | 29 | /// The naming of output files with respect to the path of the source file. 30 | /// 31 | /// For an input of `foo/bar/baz.proto` the following output file will be generated: 32 | /// - `FullPath`: `foo/bar/baz.grpc.swift` 33 | /// - `PathToUnderscore`: `foo_bar_baz.grpc.swift` 34 | /// - `DropPath`: `baz.grpc.swift` 35 | enum FileNaming: String { 36 | /// Replicate the input file path with the output file(s). 37 | case fullPath = "FullPath" 38 | /// Convert path directory delimiters to underscores. 39 | case pathToUnderscores = "PathToUnderscores" 40 | /// Generate output files using only the base name of the inout file, ignoring the path. 41 | case dropPath = "DropPath" 42 | } 43 | 44 | /// The visibility of the generated files. 45 | var accessLevel: AccessLevel 46 | /// Whether server code is generated. 47 | var servers: Bool 48 | /// Whether client code is generated. 49 | var clients: Bool 50 | /// Whether message code is generated. 51 | var messages: Bool 52 | /// The naming of output files with respect to the path of the source file. 53 | var fileNaming: FileNaming 54 | /// Whether imports should have explicit access levels. 55 | var accessLevelOnImports: Bool 56 | 57 | /// Specify the directory in which to search for imports. 58 | /// 59 | /// May be specified multiple times; directories will be searched in order. 60 | /// The target source directory is always appended to the import paths. 61 | var importPaths: [String] 62 | 63 | /// The path to the `protoc` binary. 64 | /// 65 | /// If this is not set, Swift Package Manager will try to find the tool itself. 66 | var protocPath: String? 67 | 68 | /// The path into which the generated source files are created. 69 | var outputPath: String 70 | } 71 | 72 | extension GenerationConfig.AccessLevel: Codable { 73 | init?(rawValue: String) { 74 | switch rawValue.lowercased() { 75 | case "internal": 76 | self = .internal 77 | case "public": 78 | self = .public 79 | case "package": 80 | self = .package 81 | default: 82 | return nil 83 | } 84 | } 85 | } 86 | 87 | extension GenerationConfig.FileNaming: Codable { 88 | init?(rawValue: String) { 89 | switch rawValue.lowercased() { 90 | case "fullpath": 91 | self = .fullPath 92 | case "pathtounderscores": 93 | self = .pathToUnderscores 94 | case "droppath": 95 | self = .dropPath 96 | default: 97 | return nil 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Plugins/PluginsShared/PluginUtils.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import Foundation 18 | import PackagePlugin 19 | 20 | let configFileName = "grpc-swift-proto-generator-config.json" 21 | 22 | /// Derive the path to the instance of `protoc` to be used. 23 | /// - Parameters: 24 | /// - config: The supplied config. If no path is supplied then one is discovered using the `PROTOC_PATH` environment variable or the `findTool`. 25 | /// - findTool: The context-supplied tool which is used to attempt to discover the path to a `protoc` binary. 26 | /// - Returns: The path to the instance of `protoc` to be used. 27 | func deriveProtocPath( 28 | using config: GenerationConfig, 29 | tool findTool: (String) throws -> PackagePlugin.PluginContext.Tool 30 | ) throws -> URL { 31 | if let configuredProtocPath = config.protocPath { 32 | return URL(fileURLWithPath: configuredProtocPath) 33 | } else if let environmentPath = ProcessInfo.processInfo.environment["PROTOC_PATH"] { 34 | // The user set the env variable, so let's take that 35 | return URL(fileURLWithPath: environmentPath) 36 | } else { 37 | // The user didn't set anything so let's try see if Swift Package Manager can find a binary for us 38 | return try findTool("protoc").url 39 | } 40 | } 41 | 42 | /// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin. 43 | /// - Parameters: 44 | /// - config: The config for this operation. 45 | /// - fileNaming: The file naming scheme to be used. 46 | /// - inputFiles: The input `.proto` files. 47 | /// - protoDirectoryPaths: The directories in which `protoc` will look for imports. 48 | /// - protocGenSwiftPath: The path to the `protoc-gen-swift` `protoc` plugin. 49 | /// - outputDirectory: The directory in which generated source files are created. 50 | /// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-swift` `protoc` plugin. 51 | func constructProtocGenSwiftArguments( 52 | config: GenerationConfig, 53 | fileNaming: GenerationConfig.FileNaming?, 54 | inputFiles: [URL], 55 | protoDirectoryPaths: [String], 56 | protocGenSwiftPath: URL, 57 | outputDirectory: URL 58 | ) -> [String] { 59 | var protocArgs = [ 60 | "--plugin=protoc-gen-swift=\(protocGenSwiftPath.absoluteStringNoScheme)", 61 | "--swift_out=\(outputDirectory.absoluteStringNoScheme)", 62 | ] 63 | 64 | for path in protoDirectoryPaths { 65 | protocArgs.append("--proto_path=\(path)") 66 | } 67 | 68 | protocArgs.append("--swift_opt=Visibility=\(config.accessLevel.rawValue)") 69 | protocArgs.append("--swift_opt=FileNaming=\(config.fileNaming.rawValue)") 70 | protocArgs.append("--swift_opt=UseAccessLevelOnImports=\(config.accessLevelOnImports)") 71 | protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme }) 72 | 73 | return protocArgs 74 | } 75 | 76 | /// Construct the arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift-2` `protoc` plugin. 77 | /// - Parameters: 78 | /// - config: The config for this operation. 79 | /// - fileNaming: The file naming scheme to be used. 80 | /// - inputFiles: The input `.proto` files. 81 | /// - protoDirectoryPaths: The directories in which `protoc` will look for imports. 82 | /// - protocGenGRPCSwiftPath: The path to the `protoc-gen-grpc-swift-2` `protoc` plugin. 83 | /// - outputDirectory: The directory in which generated source files are created. 84 | /// - Returns: The constructed arguments to be passed to `protoc` when invoking the `protoc-gen-grpc-swift-2` `protoc` plugin. 85 | func constructProtocGenGRPCSwiftArguments( 86 | config: GenerationConfig, 87 | fileNaming: GenerationConfig.FileNaming?, 88 | inputFiles: [URL], 89 | protoDirectoryPaths: [String], 90 | protocGenGRPCSwiftPath: URL, 91 | outputDirectory: URL 92 | ) -> [String] { 93 | var protocArgs = [ 94 | "--plugin=protoc-gen-grpc-swift=\(protocGenGRPCSwiftPath.absoluteStringNoScheme)", 95 | "--grpc-swift_out=\(outputDirectory.absoluteStringNoScheme)", 96 | ] 97 | 98 | for path in protoDirectoryPaths { 99 | protocArgs.append("--proto_path=\(path)") 100 | } 101 | 102 | protocArgs.append("--grpc-swift_opt=Visibility=\(config.accessLevel.rawValue.capitalized)") 103 | protocArgs.append("--grpc-swift_opt=Server=\(config.servers)") 104 | protocArgs.append("--grpc-swift_opt=Client=\(config.clients)") 105 | protocArgs.append("--grpc-swift_opt=FileNaming=\(config.fileNaming.rawValue)") 106 | protocArgs.append("--grpc-swift_opt=UseAccessLevelOnImports=\(config.accessLevelOnImports)") 107 | protocArgs.append(contentsOf: inputFiles.map { $0.absoluteStringNoScheme }) 108 | 109 | return protocArgs 110 | } 111 | 112 | extension URL { 113 | /// Returns `URL.absoluteString` with the `file://` scheme prefix removed 114 | /// 115 | /// Note: This method also removes percent-encoded UTF-8 characters 116 | var absoluteStringNoScheme: String { 117 | var absoluteString = self.absoluteString.removingPercentEncoding ?? self.absoluteString 118 | absoluteString.trimPrefix("file://") 119 | return absoluteString 120 | } 121 | } 122 | 123 | enum Stderr { 124 | private static let newLine = "\n".data(using: .utf8)! 125 | 126 | static func print(_ message: String) { 127 | if let data = message.data(using: .utf8) { 128 | FileHandle.standardError.write(data) 129 | FileHandle.standardError.write(Self.newLine) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC Swift Protobuf 2 | 3 | This repository contains integrations with [SwiftProtobuf][gh-swift-protobuf] 4 | for [gRPC Swift][gh-grpc-swift-protobuf]. 5 | 6 | - 📚 **Documentation** is available on the [Swift Package Index][spi-grpc-swift-protobuf] 7 | - 🎓 **Tutorials** are available in the documentation for `grpc/grpc-swift` on 8 | the [Swift Package Index][spi-grpc-swift]. 9 | - 💻 **Examples** are available in the `Examples` directory of the 10 | [`grpc/grpc-swift`](https://github.com/grpc/grpc-swift) repository 11 | - 🚀 **Contributions** are welcome, please see [CONTRIBUTING.md](CONTRIBUTING.md) 12 | - 🪪 **License** is Apache 2.0, repeated in [LICENSE](License) 13 | - 🔒 **Security** issues should be reported via the process in [SECURITY.md](SECURITY.md) 14 | 15 | [gh-swift-protobuf]: https://github.com/apple/swift-protobuf 16 | [gh-grpc-swift-protobuf]: https://github.com/grpc/grpc-swift-protobuf 17 | [spi-grpc-swift]: https://swiftpackageindex.com/grpc/grpc-swift-2/documentation 18 | [spi-grpc-swift-protobuf]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation 19 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Please refer to [SECURITY.md] in the 4 | [`grpc/grpc-swift`](https://github.com/grpc/grpc-swift) repository. 5 | -------------------------------------------------------------------------------- /Sources/CGRPCProtobuf/CGRPCProtobuf.c: -------------------------------------------------------------------------------- 1 | // Copyright 2025, gRPC Authors All rights reserved. 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 | #include "CGRPCProtobuf.h" 16 | 17 | const char *cgrprc_grpc_swift_protobuf_version() { 18 | return CGRPC_GRPC_SWIFT_PROTOBUF_VERSION; 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CGRPCProtobuf/include/CGRPCProtobuf.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025, gRPC Authors All rights reserved. 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 | #ifndef CGRPC_PROTOBUF_H_ 16 | #define CGRPC_PROTOBUF_H_ 17 | 18 | const char *cgrprc_grpc_swift_protobuf_version(); 19 | 20 | #endif // CGRPC_PROTOBUF_H_ 21 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Coding.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | public import GRPCCore 18 | public import SwiftProtobuf 19 | 20 | /// Serializes a Protobuf message into a sequence of bytes. 21 | @available(gRPCSwiftProtobuf 2.0, *) 22 | public struct ProtobufSerializer: GRPCCore.MessageSerializer { 23 | public init() {} 24 | 25 | /// Serializes a `Message` into a sequence of bytes. 26 | /// 27 | /// - Parameter message: The message to serialize. 28 | /// - Returns: An array of serialized bytes representing the message. 29 | @inlinable 30 | public func serialize(_ message: Message) throws -> Bytes { 31 | do { 32 | let adapter = try message.serializedBytes() as ContiguousBytesAdapter 33 | return adapter.bytes 34 | } catch let error { 35 | throw RPCError( 36 | code: .invalidArgument, 37 | message: "Can't serialize message of type \(type(of: message)).", 38 | cause: error 39 | ) 40 | } 41 | } 42 | } 43 | 44 | /// Deserializes a sequence of bytes into a Protobuf message. 45 | @available(gRPCSwiftProtobuf 2.0, *) 46 | public struct ProtobufDeserializer: GRPCCore.MessageDeserializer { 47 | public init() {} 48 | 49 | /// Deserializes a sequence of bytes into a `Message`. 50 | /// 51 | /// - Parameter serializedMessageBytes: The array of bytes to deserialize. 52 | /// - Returns: The deserialized message. 53 | @inlinable 54 | public func deserialize( 55 | _ serializedMessageBytes: Bytes 56 | ) throws -> Message { 57 | do { 58 | let message = try Message(serializedBytes: ContiguousBytesAdapter(serializedMessageBytes)) 59 | return message 60 | } catch let error { 61 | throw RPCError( 62 | code: .invalidArgument, 63 | message: "Can't deserialize to message of type \(Message.self).", 64 | cause: error 65 | ) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/ContiguousBytesAdapter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025, gRPC Authors All rights reserved. 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 | 17 | public import GRPCCore // internal but @usableFromInline 18 | public import SwiftProtobuf // internal but @usableFromInline 19 | 20 | /// Brides between `GRPCContiguousBytes` and `SwiftProtobufContiguousBytes` which have the same 21 | /// requirements. 22 | /// 23 | /// This is necessary as `SwiftProtobufContiguousBytes` can't be the protocol in the gRPC API (as 24 | /// it'd require a dependency on Protobuf in the core package), and `GRPCContiguousBytes` can't 25 | /// refine `SwiftProtobufContiguousBytes` for the same reason. 26 | @usableFromInline 27 | @available(gRPCSwiftProtobuf 2.0, *) 28 | struct ContiguousBytesAdapter< 29 | Bytes: GRPCContiguousBytes 30 | >: GRPCContiguousBytes, SwiftProtobufContiguousBytes { 31 | @usableFromInline 32 | var bytes: Bytes 33 | 34 | @inlinable 35 | init(_ bytes: Bytes) { 36 | self.bytes = bytes 37 | } 38 | 39 | @inlinable 40 | init(repeating: UInt8, count: Int) { 41 | self.bytes = Bytes(repeating: repeating, count: count) 42 | } 43 | 44 | @inlinable 45 | init(_ sequence: some Sequence) { 46 | self.bytes = Bytes(sequence) 47 | } 48 | 49 | @inlinable 50 | var count: Int { 51 | self.bytes.count 52 | } 53 | 54 | @inlinable 55 | func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { 56 | try self.bytes.withUnsafeBytes(body) 57 | } 58 | 59 | @inlinable 60 | mutating func withUnsafeMutableBytes( 61 | _ body: (UnsafeMutableRawBufferPointer) throws -> R 62 | ) rethrows -> R { 63 | try self.bytes.withUnsafeMutableBytes(body) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Documentation.docc/Articles/API-stability-of-generated-code.md: -------------------------------------------------------------------------------- 1 | # API stability of generated code 2 | 3 | Understand the impact of changes you make to your Protocol Buffers files on the 4 | generated Swift code. 5 | 6 | ## Overview 7 | 8 | The API of the generated code depends on three factors: 9 | 10 | - The contents of the source `.proto` file. 11 | - The options you use when generating the code. 12 | - The code generator (the `protoc-gen-grpc-swift-2` plugin for `protoc`). 13 | 14 | While this document applies specifically to the gRPC code generated and *not* 15 | code for messages used as inputs and outputs of each method, the concepts still 16 | broadly apply. 17 | 18 | Some of the concepts used in this document are described in more detail in 19 | . 20 | 21 | ## The source .proto file 22 | 23 | The source `.proto` file defines the API of your service. You'll likely provide 24 | it to users so that they can generate clients from it. In order to maintain API 25 | stability for your service and for clients you must adhere to the following 26 | rules: 27 | 28 | 1. You mustn't change the `package` the service is defined in. 29 | 2. You mustn't change or add the `swift_prefix` option. 30 | 3. You mustn't remove or change the name of any services in your `.proto` file. 31 | 4. You mustn't remove or change the name of any RPCs in your `.proto` file. 32 | 5. You mustn't change the message types used by any RPCs in your `.proto` file. 33 | 34 | Failure to follow these will result in changes in the generated code which can 35 | result in build failures for users. 36 | 37 | Whilst this sounds restrictive you may do the following: 38 | 39 | 1. You may add a new RPC to an existing service in your `.proto` file. 40 | 2. You may add a new service to your `.proto` file (however it is recommended 41 | that you define a single service per `.proto` file). 42 | 43 | ## The options you use for generating code 44 | 45 | Code you generate into your Swift Package becomes part of the API of your 46 | package. You must therefore ensure that downstream users of your package aren't 47 | impacted by the options you use when generating code. 48 | 49 | By default code is generated at the `internal` access level and therefore not 50 | part of the public API. You must explicitly opt in to generating code at the 51 | `public` access level. If you do this then you must be aware that changing what 52 | is generated (clients, servers) affects the public API, as does the access level 53 | of the generated code. 54 | 55 | If you need to validate whether your API has changed you can use tools like 56 | Swift Package Manager's API breakage diagnostic (`swift package 57 | diagnose-api-breaking-changes`.) In general you should prefer providing users 58 | with the service's `.proto` file so that they can generate clients, or provide a 59 | library which wraps the client to hide the API of the generated code. 60 | 61 | ## The code generator 62 | 63 | The gRPC Swift maintainers may need to evolve the generated code over time. This 64 | will be done in a source-compatible way. 65 | 66 | If APIs are no longer suitable then they may be deprecated in favour of new 67 | ones. Within a major version of the package existing API won't be removed 68 | and deprecated APIs will continue to function. 69 | 70 | If the generator introduces new ways to generate code which are incompatible 71 | with the previously generated code then they will require explicit opt-in via an 72 | option. 73 | 74 | As gRPC Swift is developed the generated code may need to rely on newer 75 | functionality from its runtime counterparts (`GRPCCore` and `GRPCProtobuf`). 76 | This means that you should use the versions of `protoc-gen-grpc-swift-2` and 77 | `protoc-gen-swift` resolved with your package rather than getting them from an 78 | out-of-band (such as `homebrew`). 79 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Documentation.docc/Articles/Installing-protoc.md: -------------------------------------------------------------------------------- 1 | # Installing protoc 2 | 3 | Learn how to install `protoc`, the Protocol Buffers compiler. 4 | 5 | ## Overview 6 | 7 | The Protocol Buffers compiler is a command line tool for generating source code from `.proto` 8 | files and is required to generate gRPC stubs and messages. You can learn more about it on the 9 | [Protocol Buffers website](https://protobuf.dev/). 10 | 11 | You can install `protoc` in a number of ways including: 12 | 13 | 1. Via a package manager, 14 | 2. By downloading the binary. 15 | 16 | ### Install via a package manager 17 | 18 | Using a package manager is the easiest way to install `protoc`. 19 | 20 | On macOS you can use [Homebrew](https://brew.sh): 21 | 22 | ```sh 23 | brew install protobuf 24 | ``` 25 | 26 | On Ubuntu and Debian you can use `apt`: 27 | 28 | ```sh 29 | apt update && apt install -y protobuf-compiler 30 | ``` 31 | 32 | On Fedora you can use `dnf`: 33 | 34 | ```sh 35 | dnf install -y protobuf-compiler 36 | ``` 37 | 38 | ### Installing a pre-built binary 39 | 40 | If you're unable to use a package manager to install `protoc` then you may be able 41 | to download a pre-built binary from the [Protocol Buffers GitHub 42 | repository](https://github.com/protocolbuffers/protobuf). 43 | 44 | First, find and download the appropriate binary for your system from the 45 | [releases](https://github.com/protocolbuffers/protobuf/releases) page. 46 | 47 | Next, unzip the artifact to a directory called `protoc`: 48 | 49 | ```sh 50 | unzip /path/to/downloaded/protoc-{VERSION}-{OS}.zip -d protoc 51 | ``` 52 | 53 | Finally, move `protoc/bin/protoc` to somewhere in your `$PATH` such as `/usr/local/bin`: 54 | 55 | ```sh 56 | mv protoc/bin/protoc /usr/local/bin 57 | ``` 58 | 59 | You can now remove the `protoc` directory. 60 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Documentation.docc/Articles/Public-services-with-private-implementations.md: -------------------------------------------------------------------------------- 1 | # Public services with private implementations 2 | 3 | Learn how to create a `public` gRPC service with private implementation details. 4 | 5 | ## Overview 6 | 7 | It's not uncommon for a library to provide a gRPC service as part of its API. 8 | For example, the gRPC Swift Extras package provides implementations of the gRPC 9 | health and reflection services. Making the implementation of a service `public` 10 | would require its generated gRPC and message types to also be `public`. This is 11 | undesirable as it leaks implementation details into the public API of the 12 | package. This article explains how to keep the generated types private while 13 | making the service available as part of the public API. 14 | 15 | ## Hiding the implementation 16 | 17 | You can hide the implementation details of your service by providing a wrapper 18 | type conforming to `RegistrableRPCService`. This is the protocol used by 19 | `GRPCServer` to register service methods with the server's router. Implementing 20 | `RegistrableRPCService` is straightforward and can delegate to the underlying 21 | service. This is demonstrated in the following code: 22 | 23 | ```swift 24 | public struct GreeterService: RegistrableRPCService { 25 | private var base: Greeter 26 | 27 | public init() { 28 | self.base = Greeter() 29 | } 30 | 31 | public func registerMethods( 32 | with router: inout RPCRouter 33 | ) where Transport: ServerTransport { 34 | self.base.registerMethods(with: &router) 35 | } 36 | } 37 | ``` 38 | 39 | In this example `Greeter` implements the underlying service and would conform to 40 | the generated service protocol but would have a non-public access level. 41 | `GreeterService` is a public wrapper type conforming to `RegistrableRPCService` 42 | which implements its only requirement, `registerMethods(with:)`, by calling 43 | through to the underlying implementation. The result is a service which can be 44 | registered with a server where none of the generated types are part of the 45 | public API. 46 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Documentation.docc/Articles/Understanding-the-generated-code.md: -------------------------------------------------------------------------------- 1 | # Understanding the generated code 2 | 3 | Understand what code is generated by `protoc-gen-grpc-swift-2` from a `.proto` 4 | file and how to use it. 5 | 6 | ## Overview 7 | 8 | The gRPC Swift Protobuf package provides a plugin to the Protocol Buffers 9 | Compiler (`protoc`) called `protoc-gen-grpc-swift-2`. The plugin is responsible 10 | for generating the gRPC specific code for services defined in a `.proto` file. 11 | 12 | ### Package namespace 13 | 14 | Most `.proto` files contain a `package` directive near the start of the file 15 | describing the namespace it belongs to. Here's an example: 16 | 17 | ```proto 18 | package foo.bar.v1; 19 | ``` 20 | 21 | The package name "foo.bar.v1" is important as it is used as a prefix for 22 | generated types. The default behaviour is to replace periods with underscores 23 | and to capitalize each word and add a trailing underscore. For this package the 24 | prefix is "Foo\_Bar\_V1\_". If you don't declare a package then the prefix will be 25 | the empty string. 26 | 27 | You can override the prefix by setting the `swift_prefix` option: 28 | 29 | ```proto 30 | option swift_prefix = "FooBarV1"; 31 | 32 | package foo.bar.v1; 33 | ``` 34 | 35 | The prefix for types in this file would be "FooBarV1" instead of "Foo\_Bar\_V1\_". 36 | 37 | ### Service namespace 38 | 39 | For each service declared in your `.proto` file, gRPC will generate a caseless 40 | `enum` which is a namespace holding the generated protocols and types. The name 41 | of this `enum` is `{PREFIX}{SERVICE}` where `{PREFIX}` is as described in the 42 | previous section and `{SERVICE}` is the name of the service as declared in the 43 | `.proto` file. 44 | 45 | As an example the following definition creates a service namespace `enum` called 46 | `Foo_Bar_V1_BazService` (the `{PREFIX}` is "Foo_Bar_V1_" and `{SERVICE}` is 47 | "BazService"): 48 | 49 | ```proto 50 | package foo.bar.v1; 51 | 52 | service BazService { 53 | // ... 54 | } 55 | ``` 56 | 57 | Code generated for each service falls into three categories: 58 | 59 | 1. Service metadata, 60 | 2. Service code, and 61 | 3. Client code. 62 | 63 | #### Service metadata 64 | 65 | gRPC generates metadata for each service including a descriptor identifying the 66 | fully qualified name of the service and information about each method in the 67 | service. You likely won't need to interact directly with this information but 68 | it's available should you need to. 69 | 70 | #### Service code 71 | 72 | Within a service namespace gRPC generates three service protocols: 73 | 74 | 1. `StreamingServiceProtocol`, 75 | 2. `ServiceProtocol`, and 76 | 3. `SimpleServiceProtocol`. 77 | 78 | The full name of each protocol includes the service namespace. 79 | 80 | > Example: 81 | > 82 | > For the `BazService` in the `foo.bar.v1` package the protocols would be: 83 | > 84 | > - `Foo_Bar_V1_BazService.StreamingServiceProtocol`, 85 | > - `Foo_Bar_V1_BazService.ServiceProtocol`, and 86 | > - `Foo_Bar_V1_BazService.SimpleServiceProtocol`. 87 | 88 | Each of these protocols mirror the `service` defined in your `.proto` file with 89 | one requirement per RPC. To implement your service you must implement one of 90 | these protocols. 91 | 92 | The protocols form a hierarchy with `StreamingServiceProtocol` at the bottom and 93 | `SimpleServiceProtocol` at the top. `ServiceProtocol` refines 94 | `StreamingServiceProtocol`, and `SimpleServiceProtocol` refines 95 | `ServiceProtocol` (and `StreamingServiceProtocol` in turn). 96 | 97 | The `StreamingServiceProtocol` implements each RPC as if it were a bidirectional 98 | streaming RPC. This gives you the most flexibility at the cost of a harder to 99 | implement API. It also puts the responsibility on you to ensure that each RPC 100 | sends and receives the correct number of messages. 101 | 102 | The `ServiceProtocol` enforces that the correct number of messages are sent and 103 | received via its API. It also allows you to read request metadata and send both 104 | initial and trailing metadata. The request and response types for these 105 | requirements are in terms of `ServerRequest` and `ServerResponse`. 106 | 107 | The `SimpleServiceProtocol` also enforces the correct number of messages are 108 | sent and received via its API. However, it isn't defined in terms of 109 | `ServerRequest` or `ServerResponse` so it doesn't allow you access metadata. 110 | This limitation allows it to have the simplest API and is preferred if you don't 111 | need access to metadata. 112 | 113 | | Protocol | Enforces number of messages | Access to metadata 114 | |----------------------------|-----------------------------|------------------- 115 | | `StreamingServiceProtocol` | ✗ | ✓ 116 | | `ServiceProtocol` | ✓ | ✓ 117 | | `SimpleServiceProtocol` | ✓ | ✗ 118 | 119 | #### Client code 120 | 121 | gRPC generates two types for the client within a service namespace: 122 | 123 | 1. `ClientProtocol`, and 124 | 2. `Client`. 125 | 126 | Like the service code, the full name includes the namespace. 127 | 128 | > Example: 129 | > 130 | > For the `BazService` in the `foo.bar.v1` package the client types would be: 131 | > 132 | > - `Foo_Bar_V1_BazService.ClientProtocol`, and 133 | > - `Foo_Bar_V1_BazService.Client`. 134 | 135 | The `ClientProtocol` defines one requirement for each RPC in terms of 136 | `ClientRequest` and `ClientResponse`. You don't need to implement the protocol 137 | as `Client` provides a concrete implementation. 138 | 139 | gRPC also generates extensions on `ClientProtocol` to provide more ergonomic 140 | APIs. These include versions which provide default arguments for various 141 | parameters (like the message serializer and deserializers; call options and 142 | response handler) and versions which don't use `ClientRequest` and 143 | `ClientResponse` directly. 144 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Documentation.docc/Documentation.md: -------------------------------------------------------------------------------- 1 | # ``GRPCProtobuf`` 2 | 3 | A package integrating Swift Protobuf with gRPC Swift. 4 | 5 | ## Overview 6 | 7 | This package provides three products: 8 | - ``GRPCProtobuf``, a module providing runtime serialization and deserialization components for 9 | [SwiftProtobuf](https://github.com/apple/swift-protobuf). 10 | - `protoc-gen-grpc-swift-2`, an executable which is a plugin for `protoc`, the Protocol Buffers 11 | compiler. An article describing how to generate gRPC Swift stubs using it is available with the 12 | `grpc-swift` documentation on the [Swift Package 13 | Index](https://swiftpackageindex.com/grpc/grpc-swift/documentation). 14 | - `GRPCProtobufGenerator`, a Swift Package build plugin for generating stubs as part of the build 15 | process. 16 | 17 | 18 | ## Topics 19 | 20 | ### Essentials 21 | 22 | - 23 | - 24 | - 25 | - 26 | - 27 | 28 | ### Serialization 29 | 30 | - ``ProtobufSerializer`` 31 | - ``ProtobufDeserializer`` 32 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Errors/ErrorDetails+AnyPacking.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | internal import SwiftProtobuf 18 | 19 | /// A type which can be packed and unpacked from a `Google_Protobuf_Any` message. 20 | internal protocol GoogleProtobufAnyPackable { 21 | static var typeURL: String { get } 22 | 23 | /// Pack the value into a `Google_Protobuf_Any`, if possible. 24 | func pack() throws -> Google_Protobuf_Any 25 | 26 | /// Unpack the value from a `Google_Protobuf_Any`, if possible. 27 | init?(unpacking any: Google_Protobuf_Any) throws 28 | } 29 | 30 | /// A type which is backed by a Protobuf message. 31 | /// 32 | /// This is a convenience protocol to allow for automatic packing/unpacking of messages 33 | /// where possible. 34 | internal protocol ProtobufBacked { 35 | associatedtype Message: SwiftProtobuf.Message 36 | var storage: Message { get set } 37 | init(storage: Message) 38 | } 39 | 40 | extension GoogleProtobufAnyPackable where Self: ProtobufBacked { 41 | func pack() throws -> Google_Protobuf_Any { 42 | try .with { 43 | $0.typeURL = Self.typeURL 44 | $0.value = try self.storage.serializedBytes() 45 | } 46 | } 47 | 48 | init?(unpacking any: Google_Protobuf_Any) throws { 49 | guard let storage = try any.unpack(Message.self) else { return nil } 50 | self.init(storage: storage) 51 | } 52 | } 53 | 54 | extension Google_Protobuf_Any { 55 | func unpack(_ as: Unpacked.Type) throws -> Unpacked? { 56 | if self.isA(Unpacked.self) { 57 | return try Unpacked(serializedBytes: self.value) 58 | } else { 59 | return nil 60 | } 61 | } 62 | } 63 | 64 | @available(gRPCSwiftProtobuf 2.0, *) 65 | extension ErrorDetails { 66 | // Note: this type isn't packable into an 'Any' protobuf so doesn't conform 67 | // to 'GoogleProtobufAnyPackable' despite holding types which are packable. 68 | 69 | func pack() throws -> Google_Protobuf_Any { 70 | switch self.wrapped { 71 | case .errorInfo(let info): 72 | return try info.pack() 73 | case .retryInfo(let info): 74 | return try info.pack() 75 | case .debugInfo(let info): 76 | return try info.pack() 77 | case .quotaFailure(let info): 78 | return try info.pack() 79 | case .preconditionFailure(let info): 80 | return try info.pack() 81 | case .badRequest(let info): 82 | return try info.pack() 83 | case .requestInfo(let info): 84 | return try info.pack() 85 | case .resourceInfo(let info): 86 | return try info.pack() 87 | case .help(let info): 88 | return try info.pack() 89 | case .localizedMessage(let info): 90 | return try info.pack() 91 | case .any(let any): 92 | return any 93 | } 94 | } 95 | 96 | init(unpacking any: Google_Protobuf_Any) throws { 97 | if let unpacked = try Self.unpack(any: any) { 98 | self = unpacked 99 | } else { 100 | self = .any(any) 101 | } 102 | } 103 | 104 | private static func unpack(any: Google_Protobuf_Any) throws -> Self? { 105 | switch any.typeURL { 106 | case ErrorInfo.typeURL: 107 | if let unpacked = try ErrorInfo(unpacking: any) { 108 | return .errorInfo(unpacked) 109 | } 110 | case RetryInfo.typeURL: 111 | if let unpacked = try RetryInfo(unpacking: any) { 112 | return .retryInfo(unpacked) 113 | } 114 | case DebugInfo.typeURL: 115 | if let unpacked = try DebugInfo(unpacking: any) { 116 | return .debugInfo(unpacked) 117 | } 118 | case QuotaFailure.typeURL: 119 | if let unpacked = try QuotaFailure(unpacking: any) { 120 | return .quotaFailure(unpacked) 121 | } 122 | case PreconditionFailure.typeURL: 123 | if let unpacked = try PreconditionFailure(unpacking: any) { 124 | return .preconditionFailure(unpacked) 125 | } 126 | case BadRequest.typeURL: 127 | if let unpacked = try BadRequest(unpacking: any) { 128 | return .badRequest(unpacked) 129 | } 130 | case RequestInfo.typeURL: 131 | if let unpacked = try RequestInfo(unpacking: any) { 132 | return .requestInfo(unpacked) 133 | } 134 | case ResourceInfo.typeURL: 135 | if let unpacked = try ResourceInfo(unpacking: any) { 136 | return .resourceInfo(unpacked) 137 | } 138 | case Help.typeURL: 139 | if let unpacked = try Help(unpacking: any) { 140 | return .help(unpacked) 141 | } 142 | case LocalizedMessage.typeURL: 143 | if let unpacked = try LocalizedMessage(unpacking: any) { 144 | return .localizedMessage(unpacked) 145 | } 146 | default: 147 | return .any(any) 148 | } 149 | 150 | return nil 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Errors/ErrorDetails+CustomStringConvertible.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | @available(gRPCSwiftProtobuf 2.0, *) 18 | extension ErrorDetails: CustomStringConvertible { 19 | public var description: String { 20 | switch self.wrapped { 21 | case .errorInfo(let info): 22 | return String(describing: info) 23 | case .retryInfo(let info): 24 | return String(describing: info) 25 | case .debugInfo(let info): 26 | return String(describing: info) 27 | case .quotaFailure(let info): 28 | return String(describing: info) 29 | case .preconditionFailure(let info): 30 | return String(describing: info) 31 | case .badRequest(let info): 32 | return String(describing: info) 33 | case .requestInfo(let info): 34 | return String(describing: info) 35 | case .resourceInfo(let info): 36 | return String(describing: info) 37 | case .help(let info): 38 | return String(describing: info) 39 | case .localizedMessage(let info): 40 | return String(describing: info) 41 | case .any(let any): 42 | return String(describing: any) 43 | } 44 | } 45 | } 46 | 47 | // Some errors use protobuf messages as their storage so the default description isn't 48 | // representative 49 | 50 | @available(gRPCSwiftProtobuf 2.0, *) 51 | extension ErrorDetails.ErrorInfo: CustomStringConvertible { 52 | public var description: String { 53 | "\(Self.self)(reason: \"\(self.reason)\", domain: \"\(self.domain)\", metadata: \(self.metadata))" 54 | } 55 | } 56 | 57 | @available(gRPCSwiftProtobuf 2.0, *) 58 | extension ErrorDetails.DebugInfo: CustomStringConvertible { 59 | public var description: String { 60 | "\(Self.self)(stack: \(self.stack), detail: \"\(self.detail)\")" 61 | } 62 | } 63 | 64 | @available(gRPCSwiftProtobuf 2.0, *) 65 | extension ErrorDetails.QuotaFailure.Violation: CustomStringConvertible { 66 | public var description: String { 67 | "\(Self.self)(subject: \"\(self.subject)\", violationDescription: \"\(self.violationDescription)\")" 68 | } 69 | } 70 | 71 | @available(gRPCSwiftProtobuf 2.0, *) 72 | extension ErrorDetails.PreconditionFailure.Violation: CustomStringConvertible { 73 | public var description: String { 74 | "\(Self.self)(subject: \"\(self.subject)\", type: \"\(self.type)\", violationDescription: \"\(self.violationDescription)\")" 75 | } 76 | } 77 | 78 | @available(gRPCSwiftProtobuf 2.0, *) 79 | extension ErrorDetails.BadRequest.FieldViolation: CustomStringConvertible { 80 | public var description: String { 81 | "\(Self.self)(field: \"\(self.field)\", violationDescription: \"\(self.violationDescription)\")" 82 | } 83 | } 84 | 85 | @available(gRPCSwiftProtobuf 2.0, *) 86 | extension ErrorDetails.RequestInfo: CustomStringConvertible { 87 | public var description: String { 88 | "\(Self.self)(requestID: \"\(self.requestID)\", servingData: \"\(self.servingData)\")" 89 | } 90 | } 91 | 92 | @available(gRPCSwiftProtobuf 2.0, *) 93 | extension ErrorDetails.ResourceInfo: CustomStringConvertible { 94 | public var description: String { 95 | "\(Self.self)(name: \"\(self.name)\", owner: \"\(self.owner)\", type: \"\(self.type)\", errorDescription: \"\(self.errorDescription)\")" 96 | } 97 | } 98 | 99 | @available(gRPCSwiftProtobuf 2.0, *) 100 | extension ErrorDetails.Help.Link: CustomStringConvertible { 101 | public var description: String { 102 | "\(Self.self)(url: \"\(self.url)\", linkDescription: \"\(self.linkDescription)\")" 103 | } 104 | } 105 | 106 | @available(gRPCSwiftProtobuf 2.0, *) 107 | extension ErrorDetails.LocalizedMessage: CustomStringConvertible { 108 | public var description: String { 109 | "\(Self.self)(locale: \"\(self.locale)\", message: \"\(self.message)\")" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Errors/Generated/status.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // swift-format-ignore-file 3 | // swiftlint:disable all 4 | // 5 | // Generated by the Swift generator plugin for the protocol buffer compiler. 6 | // Source: status.proto 7 | // 8 | // For information on using the generated types, please see the documentation: 9 | // https://github.com/apple/swift-protobuf/ 10 | 11 | // Copyright 2024 Google LLC 12 | // 13 | // Licensed under the Apache License, Version 2.0 (the "License"); 14 | // you may not use this file except in compliance with the License. 15 | // You may obtain a copy of the License at 16 | // 17 | // http://www.apache.org/licenses/LICENSE-2.0 18 | // 19 | // Unless required by applicable law or agreed to in writing, software 20 | // distributed under the License is distributed on an "AS IS" BASIS, 21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | // See the License for the specific language governing permissions and 23 | // limitations under the License. 24 | 25 | internal import SwiftProtobuf 26 | 27 | // If the compiler emits an error on this type, it is because this file 28 | // was generated by a version of the `protoc` Swift plug-in that is 29 | // incompatible with the version of SwiftProtobuf to which you are linking. 30 | // Please ensure that you are building against the same version of the API 31 | // that was used to generate this file. 32 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 33 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 34 | typealias Version = _2 35 | } 36 | 37 | /// The `Status` type defines a logical error model that is suitable for 38 | /// different programming environments, including REST APIs and RPC APIs. It is 39 | /// used by [gRPC](https://github.com/grpc). Each `Status` message contains 40 | /// three pieces of data: error code, error message, and error details. 41 | /// 42 | /// You can find out more about this error model and how to work with it in the 43 | /// [API Design Guide](https://cloud.google.com/apis/design/errors). 44 | struct Google_Rpc_Status: Sendable { 45 | // SwiftProtobuf.Message conformance is added in an extension below. See the 46 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 47 | // methods supported on all messages. 48 | 49 | /// The status code, which should be an enum value of 50 | /// [google.rpc.Code][google.rpc.Code]. 51 | var code: Int32 = 0 52 | 53 | /// A developer-facing error message, which should be in English. Any 54 | /// user-facing error message should be localized and sent in the 55 | /// [google.rpc.Status.details][google.rpc.Status.details] field, or localized 56 | /// by the client. 57 | var message: String = String() 58 | 59 | /// A list of messages that carry the error details. There is a common set of 60 | /// message types for APIs to use. 61 | var details: [SwiftProtobuf.Google_Protobuf_Any] = [] 62 | 63 | var unknownFields = SwiftProtobuf.UnknownStorage() 64 | 65 | init() {} 66 | } 67 | 68 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 69 | 70 | fileprivate let _protobuf_package = "google.rpc" 71 | 72 | extension Google_Rpc_Status: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 73 | static let protoMessageName: String = _protobuf_package + ".Status" 74 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 75 | 1: .same(proto: "code"), 76 | 2: .same(proto: "message"), 77 | 3: .same(proto: "details"), 78 | ] 79 | 80 | mutating func decodeMessage(decoder: inout D) throws { 81 | while let fieldNumber = try decoder.nextFieldNumber() { 82 | // The use of inline closures is to circumvent an issue where the compiler 83 | // allocates stack space for every case branch when no optimizations are 84 | // enabled. https://github.com/apple/swift-protobuf/issues/1034 85 | switch fieldNumber { 86 | case 1: try { try decoder.decodeSingularInt32Field(value: &self.code) }() 87 | case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() 88 | case 3: try { try decoder.decodeRepeatedMessageField(value: &self.details) }() 89 | default: break 90 | } 91 | } 92 | } 93 | 94 | func traverse(visitor: inout V) throws { 95 | if self.code != 0 { 96 | try visitor.visitSingularInt32Field(value: self.code, fieldNumber: 1) 97 | } 98 | if !self.message.isEmpty { 99 | try visitor.visitSingularStringField(value: self.message, fieldNumber: 2) 100 | } 101 | if !self.details.isEmpty { 102 | try visitor.visitRepeatedMessageField(value: self.details, fieldNumber: 3) 103 | } 104 | try unknownFields.traverse(visitor: &visitor) 105 | } 106 | 107 | static func ==(lhs: Google_Rpc_Status, rhs: Google_Rpc_Status) -> Bool { 108 | if lhs.code != rhs.code {return false} 109 | if lhs.message != rhs.message {return false} 110 | if lhs.details != rhs.details {return false} 111 | if lhs.unknownFields != rhs.unknownFields {return false} 112 | return true 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Errors/GoogleRPCStatus.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | public import GRPCCore 18 | public import SwiftProtobuf 19 | 20 | /// An error containing structured details which can be delivered to the client. 21 | /// 22 | /// This error follows the "richer error model" detailed in the 23 | /// [gRPC error guide](https://grpc.io/docs/guides/error/) and 24 | /// [Google AIP-193](https://google.aip.dev/193). 25 | /// 26 | /// Like an `RPCError`, this error has a `code` and `message`. However it also includes 27 | /// a list of structured error details which can be propagated to clients. A set of standard 28 | /// details are provided by ``ErrorDetails``. 29 | /// 30 | /// As a client you can extract this error from an `RPCError` using `unpackGoogleRPCStatus()`. 31 | /// 32 | /// > Implementation details: 33 | /// > 34 | /// > The error information is transmitted to clients in the trailing metadata of an RPC. It is 35 | /// > inserted into the metadata keyed by "grpc-status-details-bin". The value of the metadata is 36 | /// > the serialized bytes of a "google.rpc.Status" protocol buffers message containing the status 37 | /// > code, message, and details. 38 | @available(gRPCSwiftProtobuf 2.0, *) 39 | public struct GoogleRPCStatus: Error, Hashable { 40 | /// A code representing the high-level domain of the error. 41 | public var code: RPCError.Code 42 | 43 | /// A developer-facing error message, which should be in English. 44 | /// 45 | /// Any user-facing error message should be localized and sent in the `details` field 46 | /// or localized by the client. 47 | public var message: String 48 | 49 | /// A list of messages that carry the error details. 50 | public var details: [ErrorDetails] 51 | 52 | /// Create a new Google RPC Status error. 53 | /// 54 | /// - Parameters: 55 | /// - code: A code representing the high-level domain of the error. 56 | /// - message: A developer-facing error message. 57 | /// - details: A list of messages that carry the error details. 58 | public init(code: RPCError.Code, message: String, details: [ErrorDetails]) { 59 | self.code = code 60 | self.message = message 61 | self.details = details 62 | } 63 | 64 | /// Create a new Google RPC Status error. 65 | /// 66 | /// - Parameters: 67 | /// - code: A code representing the high-level domain of the error. 68 | /// - message: A developer-facing error message. 69 | /// - details: A list of messages that carry the error details. 70 | public init(code: RPCError.Code, message: String, details: ErrorDetails...) { 71 | self.code = code 72 | self.message = message 73 | self.details = details 74 | } 75 | } 76 | 77 | @available(gRPCSwiftProtobuf 2.0, *) 78 | extension GoogleRPCStatus { 79 | /// Creates a new message by decoding the given `SwiftProtobufContiguousBytes` value 80 | /// containing a serialized message in Protocol Buffer binary format. 81 | /// 82 | /// - Parameters: 83 | /// - bytes: The binary-encoded message data to decode. 84 | /// - extensions: An `ExtensionMap` used to look up and decode any 85 | /// extensions in this message or messages nested within this message's 86 | /// fields. 87 | /// - partial: If `false` (the default), this method will check if the `Message` 88 | /// is initialized after decoding to verify that all required fields are present. 89 | /// If any are missing, this method throws `BinaryDecodingError`. 90 | /// - options: The `BinaryDecodingOptions` to use. 91 | /// - Throws: `BinaryDecodingError` if decoding fails. 92 | public init( 93 | serializedBytes bytes: Bytes, 94 | extensions: (any ExtensionMap)? = nil, 95 | partial: Bool = false, 96 | options: BinaryDecodingOptions = BinaryDecodingOptions() 97 | ) throws { 98 | let status = try Google_Rpc_Status( 99 | serializedBytes: bytes, 100 | extensions: extensions, 101 | partial: partial, 102 | options: options 103 | ) 104 | 105 | let statusCode = Status.Code(rawValue: Int(status.code)) 106 | self.code = statusCode.flatMap { RPCError.Code($0) } ?? .unknown 107 | self.message = status.message 108 | self.details = try status.details.map { try ErrorDetails(unpacking: $0) } 109 | } 110 | 111 | /// Returns a `SwiftProtobufContiguousBytes` instance containing the Protocol Buffer binary 112 | /// format serialization of the message. 113 | /// 114 | /// - Parameters: 115 | /// - partial: If `false` (the default), this method will check 116 | /// `Message.isInitialized` before encoding to verify that all required 117 | /// fields are present. If any are missing, this method throws. 118 | /// `BinaryEncodingError/missingRequiredFields`. 119 | /// - options: The `BinaryEncodingOptions` to use. 120 | /// - Returns: A `SwiftProtobufContiguousBytes` instance containing the binary serialization 121 | /// of the message. 122 | /// 123 | /// - Throws: `SwiftProtobufError` or `BinaryEncodingError` if encoding fails. 124 | public func serializedBytes( 125 | partial: Bool = false, 126 | options: BinaryEncodingOptions = BinaryEncodingOptions() 127 | ) throws -> Bytes { 128 | let status = try Google_Rpc_Status.with { 129 | $0.code = Int32(self.code.rawValue) 130 | $0.message = self.message 131 | $0.details = try self.details.map { try $0.pack() } 132 | } 133 | 134 | return try status.serializedBytes(partial: partial, options: options) 135 | } 136 | } 137 | 138 | @available(gRPCSwiftProtobuf 2.0, *) 139 | extension GoogleRPCStatus: RPCErrorConvertible { 140 | public var rpcErrorCode: RPCError.Code { self.code } 141 | public var rpcErrorMessage: String { self.message } 142 | public var rpcErrorMetadata: Metadata { 143 | do { 144 | let bytes: [UInt8] = try self.serializedBytes() 145 | return [Metadata.statusDetailsBinKey: .binary(bytes)] 146 | } catch { 147 | // Failed to serialize error details. Not a lot can be done here. 148 | return [:] 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/GRPCProtobuf/Errors/RPCError+GoogleRPCStatus.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | public import GRPCCore 18 | internal import SwiftProtobuf 19 | 20 | @available(gRPCSwiftProtobuf 2.0, *) 21 | extension Metadata { 22 | static let statusDetailsBinKey = "grpc-status-details-bin" 23 | } 24 | 25 | @available(gRPCSwiftProtobuf 2.0, *) 26 | extension RPCError { 27 | /// Unpack a ``GoogleRPCStatus`` error from the error metadata. 28 | /// 29 | /// - Throws: If status details exist in the metadata but they couldn't be unpacked to 30 | /// a ``GoogleRPCStatus``. 31 | /// - Returns: The unpacked ``GoogleRPCStatus`` or `nil` if the metadata didn't contain any 32 | /// status details. 33 | public func unpackGoogleRPCStatus() throws -> GoogleRPCStatus? { 34 | let values = self.metadata[binaryValues: Metadata.statusDetailsBinKey] 35 | guard let bytes = values.first(where: { _ in true }) else { return nil } 36 | return try GoogleRPCStatus(serializedBytes: bytes) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/GRPCProtobufCodeGen/CamelCaser.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | package enum CamelCaser { 18 | /// Converts a string from upper camel case to lower camel case. 19 | package static func toLowerCamelCase(_ input: String) -> String { 20 | guard let indexOfFirstLowercase = input.firstIndex(where: { $0.isLowercase }) else { 21 | return input.lowercased() 22 | } 23 | 24 | if indexOfFirstLowercase == input.startIndex { 25 | // `input` already begins with a lower case letter. As in: "importCSV". 26 | return input 27 | } else if indexOfFirstLowercase == input.index(after: input.startIndex) { 28 | // The second character in `input` is lower case. As in: "ImportCSV". 29 | // returns "importCSV" 30 | return input[input.startIndex].lowercased() + input[indexOfFirstLowercase...] 31 | } else { 32 | // The first lower case character is further within `input`. Tentatively, `input` begins 33 | // with one or more abbreviations. Therefore, the last encountered upper case character 34 | // could be the beginning of the next word. As in: "FOOBARImportCSV". 35 | 36 | let leadingAbbreviation = input[.. CodeGenerationRequest { 55 | let namer = SwiftProtobufNamer( 56 | currentFile: descriptor, 57 | protoFileToModuleMappings: self.protoToModuleMappings 58 | ) 59 | 60 | var header = descriptor.header 61 | // Ensuring there is a blank line after the header. 62 | if !header.isEmpty && !header.hasSuffix("\n\n") { 63 | header.append("\n") 64 | } 65 | 66 | let leadingTrivia = """ 67 | // DO NOT EDIT. 68 | // swift-format-ignore-file 69 | // swiftlint:disable all 70 | // 71 | // Generated by the gRPC Swift generator plugin for the protocol buffer compiler. 72 | // Source: \(descriptor.name) 73 | // 74 | // For information on using the generated types, please see the documentation: 75 | // https://github.com/grpc/grpc-swift 76 | 77 | """ 78 | 79 | let services = descriptor.services.map { 80 | GRPCCodeGen.ServiceDescriptor( 81 | descriptor: $0, 82 | package: descriptor.package, 83 | protobufNamer: namer, 84 | file: descriptor 85 | ) 86 | } 87 | 88 | return CodeGenerationRequest( 89 | fileName: descriptor.name, 90 | leadingTrivia: header + leadingTrivia, 91 | dependencies: self.codeDependencies(file: descriptor), 92 | services: services, 93 | makeSerializerCodeSnippet: { messageType in 94 | "\(self.moduleNames.grpcProtobuf).ProtobufSerializer<\(messageType)>()" 95 | }, 96 | makeDeserializerCodeSnippet: { messageType in 97 | "\(self.moduleNames.grpcProtobuf).ProtobufDeserializer<\(messageType)>()" 98 | } 99 | ) 100 | } 101 | } 102 | 103 | @available(gRPCSwiftProtobuf 2.0, *) 104 | extension ProtobufCodeGenParser { 105 | fileprivate func codeDependencies(file: FileDescriptor) -> [Dependency] { 106 | guard file.services.count > 0 else { 107 | return [] 108 | } 109 | 110 | var codeDependencies: [Dependency] = [ 111 | Dependency(module: self.moduleNames.grpcProtobuf, accessLevel: .internal) 112 | ] 113 | // If there's a dependency on a bundled proto then add the SwiftProtobuf import. 114 | // 115 | // Importing SwiftProtobuf unconditionally results in warnings in the generated 116 | // code if access-levels are used on imports and no bundled protos are used. 117 | let dependsOnBundledProto = file.dependencies.contains { descriptor in 118 | SwiftProtobufInfo.isBundledProto(file: descriptor) 119 | } 120 | 121 | if dependsOnBundledProto { 122 | let dependency = Dependency( 123 | module: self.moduleNames.swiftProtobuf, 124 | accessLevel: self.accessLevel 125 | ) 126 | codeDependencies.append(dependency) 127 | } 128 | 129 | // Adding as dependencies the modules containing generated code or types for 130 | // '.proto' files imported in the '.proto' file we are parsing. 131 | codeDependencies.append( 132 | contentsOf: (self.protoToModuleMappings.neededModules(forFile: file) ?? []).map { 133 | Dependency(module: $0, accessLevel: self.accessLevel) 134 | } 135 | ) 136 | // Adding extra imports passed in as an option to the plugin. 137 | codeDependencies.append( 138 | contentsOf: self.extraModuleImports.sorted().map { 139 | Dependency(module: $0, accessLevel: self.accessLevel) 140 | } 141 | ) 142 | return codeDependencies 143 | } 144 | } 145 | 146 | @available(gRPCSwiftProtobuf 2.0, *) 147 | extension GRPCCodeGen.ServiceDescriptor { 148 | fileprivate init( 149 | descriptor: SwiftProtobufPluginLibrary.ServiceDescriptor, 150 | package: String, 151 | protobufNamer: SwiftProtobufNamer, 152 | file: FileDescriptor 153 | ) { 154 | let methods = descriptor.methods.map { 155 | GRPCCodeGen.MethodDescriptor( 156 | descriptor: $0, 157 | protobufNamer: protobufNamer 158 | ) 159 | } 160 | 161 | let typePrefix = protobufNamer.typePrefix(forFile: file) 162 | let name = ServiceName( 163 | identifyingName: descriptor.fullName, 164 | // The service name from the '.proto' file is expected to be in upper camel case 165 | typeName: typePrefix + descriptor.name, 166 | propertyName: protobufNamer.typePrefixProperty(file: file) + descriptor.name 167 | ) 168 | 169 | let documentation = descriptor.protoSourceComments() 170 | self.init(documentation: documentation, name: name, methods: methods) 171 | } 172 | } 173 | 174 | @available(gRPCSwiftProtobuf 2.0, *) 175 | extension GRPCCodeGen.MethodDescriptor { 176 | fileprivate init( 177 | descriptor: SwiftProtobufPluginLibrary.MethodDescriptor, 178 | protobufNamer: SwiftProtobufNamer 179 | ) { 180 | let name = MethodName( 181 | identifyingName: descriptor.name, 182 | // The method name from the '.proto' file is expected to be in upper camel case 183 | typeName: descriptor.name, 184 | functionName: CamelCaser.toLowerCamelCase(descriptor.name) 185 | ) 186 | let documentation = descriptor.protoSourceComments() 187 | self.init( 188 | documentation: documentation, 189 | name: name, 190 | isInputStreaming: descriptor.clientStreaming, 191 | isOutputStreaming: descriptor.serverStreaming, 192 | inputType: protobufNamer.fullName(message: descriptor.inputType), 193 | outputType: protobufNamer.fullName(message: descriptor.outputType) 194 | ) 195 | } 196 | } 197 | 198 | extension FileDescriptor { 199 | fileprivate var header: String { 200 | var header = String() 201 | // Field number used to collect the syntax field which is usually the first 202 | // declaration in a.proto file. 203 | // See more here: 204 | // https://github.com/apple/swift-protobuf/blob/main/Protos/SwiftProtobuf/google/protobuf/descriptor.proto 205 | let syntaxPath = IndexPath(index: 12) 206 | if let syntaxLocation = self.sourceCodeInfoLocation(path: syntaxPath) { 207 | header = syntaxLocation.asSourceComment( 208 | commentPrefix: "///", 209 | leadingDetachedPrefix: "//" 210 | ) 211 | } 212 | return header 213 | } 214 | } 215 | 216 | extension SwiftProtobufNamer { 217 | internal func typePrefixProperty(file: FileDescriptor) -> String { 218 | let typePrefix = self.typePrefix(forFile: file) 219 | let lowercased = typePrefix.split(separator: "_").map { component in 220 | NamingUtils.toLowerCamelCase(String(component)) 221 | } 222 | 223 | let joined = lowercased.joined(separator: "_") 224 | if typePrefix.hasSuffix("_"), !joined.hasSuffix("_") { 225 | // Add the trailing "_" if it was dropped. 226 | return joined + "_" 227 | } else { 228 | return joined 229 | } 230 | } 231 | } 232 | 233 | extension String { 234 | internal func trimTrailingUnderscores() -> String { 235 | if let index = self.lastIndex(where: { $0 != "_" }) { 236 | return String(self[...index]) 237 | } else { 238 | return "" 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Sources/GRPCProtobufCodeGen/ProtobufCodeGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | package import GRPCCodeGen 18 | package import SwiftProtobufPluginLibrary 19 | 20 | @available(gRPCSwiftProtobuf 2.0, *) 21 | package struct ProtobufCodeGenerator { 22 | internal var config: ProtobufCodeGenerator.Config 23 | 24 | package init( 25 | config: ProtobufCodeGenerator.Config 26 | ) { 27 | self.config = config 28 | } 29 | 30 | package func generateCode( 31 | fileDescriptor: FileDescriptor, 32 | protoFileModuleMappings: ProtoFileToModuleMappings, 33 | extraModuleImports: [String], 34 | availabilityOverrides: [(os: String, version: String)] = [] 35 | ) throws -> String { 36 | let parser = ProtobufCodeGenParser( 37 | protoFileModuleMappings: protoFileModuleMappings, 38 | extraModuleImports: extraModuleImports, 39 | accessLevel: self.config.accessLevel, 40 | moduleNames: self.config.moduleNames 41 | ) 42 | 43 | var codeGeneratorConfig = GRPCCodeGen.CodeGenerator.Config( 44 | accessLevel: self.config.accessLevel, 45 | accessLevelOnImports: self.config.accessLevelOnImports, 46 | client: self.config.generateClient, 47 | server: self.config.generateServer, 48 | indentation: self.config.indentation 49 | ) 50 | codeGeneratorConfig.grpcCoreModuleName = self.config.moduleNames.grpcCore 51 | 52 | if availabilityOverrides.isEmpty { 53 | codeGeneratorConfig.availability = .default 54 | } else { 55 | codeGeneratorConfig.availability = .custom( 56 | availabilityOverrides.map { (os, version) in 57 | .init(os: os, version: version) 58 | } 59 | ) 60 | } 61 | 62 | let codeGenerator = GRPCCodeGen.CodeGenerator(config: codeGeneratorConfig) 63 | 64 | let codeGenerationRequest = try parser.parse(descriptor: fileDescriptor) 65 | let sourceFile = try codeGenerator.generate(codeGenerationRequest) 66 | return sourceFile.contents 67 | } 68 | } 69 | 70 | @available(gRPCSwiftProtobuf 2.0, *) 71 | extension ProtobufCodeGenerator { 72 | package struct Config { 73 | package var accessLevel: GRPCCodeGen.CodeGenerator.Config.AccessLevel 74 | package var accessLevelOnImports: Bool 75 | 76 | package var generateClient: Bool 77 | package var generateServer: Bool 78 | 79 | package var indentation: Int 80 | package var moduleNames: ModuleNames 81 | 82 | package struct ModuleNames { 83 | package var grpcCore: String 84 | package var grpcProtobuf: String 85 | package var swiftProtobuf: String 86 | 87 | package static let defaults = Self( 88 | grpcCore: "GRPCCore", 89 | grpcProtobuf: "GRPCProtobuf", 90 | swiftProtobuf: "SwiftProtobuf" 91 | ) 92 | } 93 | 94 | package static var defaults: Self { 95 | Self( 96 | accessLevel: .internal, 97 | accessLevelOnImports: false, 98 | generateClient: true, 99 | generateServer: true, 100 | indentation: 4, 101 | moduleNames: .defaults 102 | ) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/protoc-gen-grpc-swift-2/GenerateGRPC.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCCodeGen 18 | import GRPCProtobufCodeGen 19 | import SwiftProtobuf 20 | import SwiftProtobufPluginLibrary 21 | 22 | #if canImport(FoundationEssentials) 23 | import FoundationEssentials 24 | #else 25 | import Foundation 26 | #endif 27 | 28 | @main 29 | @available(gRPCSwiftProtobuf 2.0, *) 30 | final class GenerateGRPC: SwiftProtobufPluginLibrary.CodeGenerator { 31 | var version: String? { 32 | Version.versionString 33 | } 34 | 35 | var projectURL: String { 36 | "https://github.com/grpc/grpc-swift" 37 | } 38 | 39 | var supportedFeatures: [Google_Protobuf_Compiler_CodeGeneratorResponse.Feature] { 40 | [.proto3Optional, .supportsEditions] 41 | } 42 | 43 | var supportedEditionRange: ClosedRange { 44 | Google_Protobuf_Edition.proto2 ... Google_Protobuf_Edition.edition2023 45 | } 46 | 47 | // A count of generated files by desired name (actual name may differ to avoid collisions). 48 | private var generatedFileNames: [String: Int] = [:] 49 | 50 | func generate( 51 | files fileDescriptors: [FileDescriptor], 52 | parameter: any CodeGeneratorParameter, 53 | protoCompilerContext: any ProtoCompilerContext, 54 | generatorOutputs outputs: any GeneratorOutputs 55 | ) throws { 56 | let options = try GeneratorOptions(parameter: parameter) 57 | 58 | for descriptor in fileDescriptors { 59 | try self.generateV2Stubs(descriptor, options: options, outputs: outputs) 60 | } 61 | } 62 | 63 | private func generateV2Stubs( 64 | _ descriptor: FileDescriptor, 65 | options: GeneratorOptions, 66 | outputs: any GeneratorOutputs 67 | ) throws { 68 | let fileName = self.uniqueOutputFileName( 69 | fileDescriptor: descriptor, 70 | fileNamingOption: options.fileNaming 71 | ) 72 | 73 | let fileGenerator = ProtobufCodeGenerator(config: options.config) 74 | let contents = try fileGenerator.generateCode( 75 | fileDescriptor: descriptor, 76 | protoFileModuleMappings: options.protoToModuleMappings, 77 | extraModuleImports: options.extraModuleImports, 78 | availabilityOverrides: options.availabilityOverrides 79 | ) 80 | 81 | try outputs.add(fileName: fileName, contents: contents) 82 | } 83 | } 84 | 85 | @available(gRPCSwiftProtobuf 2.0, *) 86 | extension GenerateGRPC { 87 | private func uniqueOutputFileName( 88 | fileDescriptor: FileDescriptor, 89 | fileNamingOption: FileNaming, 90 | component: String = "grpc", 91 | extension: String = "swift" 92 | ) -> String { 93 | let defaultName = outputFileName( 94 | component: component, 95 | fileDescriptor: fileDescriptor, 96 | fileNamingOption: fileNamingOption, 97 | extension: `extension` 98 | ) 99 | if let count = self.generatedFileNames[defaultName] { 100 | self.generatedFileNames[defaultName] = count + 1 101 | return outputFileName( 102 | component: "\(count)." + component, 103 | fileDescriptor: fileDescriptor, 104 | fileNamingOption: fileNamingOption, 105 | extension: `extension` 106 | ) 107 | } else { 108 | self.generatedFileNames[defaultName] = 1 109 | return defaultName 110 | } 111 | } 112 | 113 | private func outputFileName( 114 | component: String, 115 | fileDescriptor: FileDescriptor, 116 | fileNamingOption: FileNaming, 117 | extension: String 118 | ) -> String { 119 | let ext = "." + component + "." + `extension` 120 | let pathParts = splitPath(pathname: fileDescriptor.name) 121 | switch fileNamingOption { 122 | case .fullPath: 123 | return pathParts.dir + pathParts.base + ext 124 | case .pathToUnderscores: 125 | let dirWithUnderscores = pathParts.dir.replacing("/", with: "_") 126 | return dirWithUnderscores + pathParts.base + ext 127 | case .dropPath: 128 | return pathParts.base + ext 129 | } 130 | } 131 | } 132 | 133 | // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift 134 | private func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) { 135 | var dir = "" 136 | var base = "" 137 | var suffix = "" 138 | 139 | for character in pathname { 140 | if character == "/" { 141 | dir += base + suffix + String(character) 142 | base = "" 143 | suffix = "" 144 | } else if character == "." { 145 | base += suffix 146 | suffix = String(character) 147 | } else { 148 | suffix += String(character) 149 | } 150 | } 151 | 152 | let validSuffix = suffix.isEmpty || suffix.first == "." 153 | if !validSuffix { 154 | base += suffix 155 | suffix = "" 156 | } 157 | return (dir: dir, base: base, suffix: suffix) 158 | } 159 | -------------------------------------------------------------------------------- /Sources/protoc-gen-grpc-swift-2/Options.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017, gRPC Authors All rights reserved. 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 | 17 | import GRPCCodeGen 18 | import GRPCProtobufCodeGen 19 | import SwiftProtobufPluginLibrary 20 | 21 | enum GenerationError: Error, CustomStringConvertible { 22 | /// Raised when parsing the parameter string and found an unknown key 23 | case unknownParameter(name: String) 24 | /// Raised when a parameter was giving an invalid value 25 | case invalidParameterValue(name: String, value: String) 26 | /// Raised to wrap another error but provide a context message. 27 | case wrappedError(message: String, error: any Error) 28 | /// The parameter isn't supported. 29 | case unsupportedParameter(name: String, message: String) 30 | 31 | var description: String { 32 | switch self { 33 | case let .unknownParameter(name): 34 | return "Unknown generation parameter '\(name)'" 35 | case let .invalidParameterValue(name, value): 36 | return "Unknown value for generation parameter '\(name)': '\(value)'" 37 | case let .wrappedError(message, error): 38 | return "\(message): \(error)" 39 | case let .unsupportedParameter(name, message): 40 | return "Unsupported parameter '\(name)': \(message)" 41 | } 42 | } 43 | } 44 | 45 | enum FileNaming: String { 46 | case fullPath = "FullPath" 47 | case pathToUnderscores = "PathToUnderscores" 48 | case dropPath = "DropPath" 49 | } 50 | 51 | @available(gRPCSwiftProtobuf 2.0, *) 52 | struct GeneratorOptions { 53 | private(set) var protoToModuleMappings = ProtoFileToModuleMappings() 54 | private(set) var fileNaming = FileNaming.fullPath 55 | private(set) var extraModuleImports: [String] = [] 56 | private(set) var availabilityOverrides: [(os: String, version: String)] = [] 57 | 58 | private(set) var config: ProtobufCodeGenerator.Config = .defaults 59 | 60 | init(parameter: any CodeGeneratorParameter) throws { 61 | try self.init(pairs: parameter.parsedPairs) 62 | } 63 | 64 | init(pairs: [(key: String, value: String)]) throws { 65 | for pair in pairs { 66 | switch pair.key { 67 | case "Visibility": 68 | if let value = GRPCCodeGen.CodeGenerator.Config.AccessLevel(protocOption: pair.value) { 69 | self.config.accessLevel = value 70 | } else { 71 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 72 | } 73 | 74 | case "Server": 75 | if let value = Bool(pair.value.lowercased()) { 76 | self.config.generateServer = value 77 | } else { 78 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 79 | } 80 | 81 | case "Client": 82 | if let value = Bool(pair.value.lowercased()) { 83 | self.config.generateClient = value 84 | } else { 85 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 86 | } 87 | 88 | case "ProtoPathModuleMappings": 89 | if !pair.value.isEmpty { 90 | do { 91 | self.protoToModuleMappings = try ProtoFileToModuleMappings(path: pair.value) 92 | } catch let e { 93 | throw GenerationError.wrappedError( 94 | message: "Parameter 'ProtoPathModuleMappings=\(pair.value)'", 95 | error: e 96 | ) 97 | } 98 | } 99 | 100 | case "FileNaming": 101 | if let value = FileNaming(rawValue: pair.value) { 102 | self.fileNaming = value 103 | } else { 104 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 105 | } 106 | 107 | case "ExtraModuleImports": 108 | if !pair.value.isEmpty { 109 | self.extraModuleImports.append(pair.value) 110 | } else { 111 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 112 | } 113 | 114 | case "GRPCModuleName": 115 | if !pair.value.isEmpty { 116 | self.config.moduleNames.grpcCore = pair.value 117 | } else { 118 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 119 | } 120 | 121 | case "GRPCProtobufModuleName": 122 | if !pair.value.isEmpty { 123 | self.config.moduleNames.grpcProtobuf = pair.value 124 | } else { 125 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 126 | } 127 | 128 | case "SwiftProtobufModuleName": 129 | if !pair.value.isEmpty { 130 | self.config.moduleNames.swiftProtobuf = pair.value 131 | } else { 132 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 133 | } 134 | 135 | case "Availability": 136 | if !pair.value.isEmpty { 137 | let parts = pair.value.split(separator: " ", maxSplits: 1) 138 | if parts.count == 2 { 139 | self.availabilityOverrides.append((os: String(parts[0]), version: String(parts[1]))) 140 | } else { 141 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 142 | } 143 | } else { 144 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 145 | } 146 | 147 | case "ReflectionData": 148 | throw GenerationError.unsupportedParameter( 149 | name: pair.key, 150 | message: """ 151 | The reflection service uses descriptor sets. Refer to the protoc docs and the \ 152 | '--descriptor_set_out' option for more information. 153 | """ 154 | ) 155 | 156 | case "UseAccessLevelOnImports": 157 | if let value = Bool(pair.value.lowercased()) { 158 | self.config.accessLevelOnImports = value 159 | } else { 160 | throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) 161 | } 162 | 163 | default: 164 | throw GenerationError.unknownParameter(name: pair.key) 165 | } 166 | } 167 | } 168 | 169 | static func parseParameter(string: String?) -> [(key: String, value: String)] { 170 | guard let string = string, !string.isEmpty else { 171 | return [] 172 | } 173 | 174 | let parts = string.split(separator: ",") 175 | 176 | // Partitions the string into the section before the = and after the = 177 | let result = parts.map { string -> (key: String, value: String) in 178 | // Finds the equal sign and exits early if none 179 | guard let index = string.firstIndex(of: "=") else { 180 | return (String(string), "") 181 | } 182 | 183 | // Creates key/value pair and trims whitespace 184 | let key = string[.. String { 198 | let trimmedSuffix = self.drop(while: { $0.isNewline || $0.isWhitespace }) 199 | let trimmed = trimmedSuffix.trimmingPrefix(while: { $0.isNewline || $0.isWhitespace }) 200 | return String(trimmed) 201 | } 202 | } 203 | 204 | @available(gRPCSwiftProtobuf 2.0, *) 205 | extension GRPCCodeGen.CodeGenerator.Config.AccessLevel { 206 | fileprivate init?(protocOption value: String) { 207 | switch value { 208 | case "Internal": 209 | self = .internal 210 | case "Public": 211 | self = .public 212 | case "Package": 213 | self = .package 214 | default: 215 | return nil 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Sources/protoc-gen-grpc-swift-2/Version.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | #if canImport(CGRPCProtobuf) 18 | private import CGRPCProtobuf 19 | #endif 20 | 21 | internal enum Version { 22 | /// The version string. 23 | internal static var versionString: String { 24 | #if canImport(CGRPCProtobuf) 25 | String(cString: cgrprc_grpc_swift_protobuf_version()) 26 | #else 27 | "unknown" 28 | #endif 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/CamelCaserTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCProtobufCodeGen 18 | import Testing 19 | 20 | @Suite("CamelCaser") 21 | struct CamelCaserTests { 22 | @Test( 23 | "Convert to lower camel case", 24 | arguments: [ 25 | ("ImportCsv", "importCsv"), 26 | ("ImportCSV", "importCSV"), 27 | ("CSVImport", "csvImport"), 28 | ("importCSV", "importCSV"), 29 | ("FOOBARImport", "foobarImport"), 30 | ("FOO_BARImport", "foo_barImport"), 31 | ("My_CSVImport", "my_CSVImport"), 32 | ("_CSVImport", "_csvImport"), 33 | ("V2Request", "v2Request"), 34 | ("V2_Request", "v2_Request"), 35 | ("CSV", "csv"), 36 | ("I", "i"), 37 | ("i", "i"), 38 | ("I_", "i_"), 39 | ("_", "_"), 40 | ("", ""), 41 | ] 42 | ) 43 | func toLowerCamelCase(_ input: String, expectedOutput: String) async throws { 44 | #expect(CamelCaser.toLowerCamelCase(input) == expectedOutput) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Generated/bar-service.pb: -------------------------------------------------------------------------------- 1 | 2 | T 3 | bar-service.proto2 4 | 5 | BarServiceJ) 6 |  7 |  8 |   9 | 10 |  11 | 12 | 13 | bproto3 -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Generated/foo-messages.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grpc/grpc-swift-protobuf/81250936869008a7f4393795652caf2452cd9ed6/Tests/GRPCProtobufCodeGenTests/Generated/foo-messages.pb -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Generated/foo-service.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grpc/grpc-swift-protobuf/81250936869008a7f4393795652caf2452cd9ed6/Tests/GRPCProtobufCodeGenTests/Generated/foo-service.pb -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Generated/test-service.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grpc/grpc-swift-protobuf/81250936869008a7f4393795652caf2452cd9ed6/Tests/GRPCProtobufCodeGenTests/Generated/test-service.pb -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Generated/wkt-service.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grpc/grpc-swift-protobuf/81250936869008a7f4393795652caf2452cd9ed6/Tests/GRPCProtobufCodeGenTests/Generated/wkt-service.pb -------------------------------------------------------------------------------- /Tests/GRPCProtobufCodeGenTests/Utilities.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import Foundation 18 | import GRPCProtobufCodeGen 19 | import SwiftProtobuf 20 | import SwiftProtobufPluginLibrary 21 | import Testing 22 | 23 | import struct GRPCCodeGen.CodeGenerationRequest 24 | import struct GRPCCodeGen.CodeGenerator 25 | 26 | protocol UsesDescriptorSet { 27 | static var descriptorSetName: String { get } 28 | static var fileDescriptorName: String { get } 29 | 30 | static var descriptorSet: DescriptorSet { get throws } 31 | static var fileDescriptor: FileDescriptor { get throws } 32 | } 33 | 34 | extension UsesDescriptorSet { 35 | static var descriptorSet: DescriptorSet { 36 | get throws { 37 | try loadDescriptorSet(named: Self.descriptorSetName) 38 | } 39 | } 40 | 41 | static var fileDescriptor: FileDescriptor { 42 | get throws { 43 | let descriptorSet = try Self.descriptorSet 44 | if let fileDescriptor = descriptorSet.fileDescriptor(named: fileDescriptorName + ".proto") { 45 | return fileDescriptor 46 | } else { 47 | throw MissingFileDescriptor() 48 | } 49 | } 50 | } 51 | } 52 | 53 | struct MissingFileDescriptor: Error {} 54 | 55 | private func loadDescriptorSet( 56 | named name: String, 57 | withExtension extension: String = "pb" 58 | ) throws -> DescriptorSet { 59 | let maybeURL = Bundle.module.url( 60 | forResource: name, 61 | withExtension: `extension`, 62 | subdirectory: "Generated" 63 | ) 64 | 65 | let url = try #require(maybeURL) 66 | let data = try Data(contentsOf: url) 67 | let descriptorSet = try Google_Protobuf_FileDescriptorSet(serializedBytes: data) 68 | return DescriptorSet(proto: descriptorSet) 69 | } 70 | 71 | @available(gRPCSwiftProtobuf 2.0, *) 72 | func parseDescriptor( 73 | _ descriptor: FileDescriptor, 74 | extraModuleImports: [String] = [], 75 | accessLevel: CodeGenerator.Config.AccessLevel = .internal 76 | ) throws -> CodeGenerationRequest { 77 | let parser = ProtobufCodeGenParser( 78 | protoFileModuleMappings: .init(), 79 | extraModuleImports: extraModuleImports, 80 | accessLevel: accessLevel, 81 | moduleNames: .defaults 82 | ) 83 | return try parser.parse(descriptor: descriptor) 84 | } 85 | -------------------------------------------------------------------------------- /Tests/GRPCProtobufTests/Errors/DetailedErrorTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCCore 18 | import GRPCInProcessTransport 19 | import GRPCProtobuf 20 | import SwiftProtobuf 21 | import Testing 22 | 23 | struct DetailedErrorTests { 24 | @Test( 25 | "Google RPC Status is transferred over the wire", 26 | arguments: [ 27 | ([], []), 28 | (["ErrorInfo"], [.errorInfo(.testValue)]), 29 | (["RetryInfo"], [.retryInfo(.testValue)]), 30 | (["DebugInfo"], [.debugInfo(.testValue)]), 31 | (["QuotaFailure"], [.quotaFailure(.testValue)]), 32 | (["PreconditionFailure"], [.preconditionFailure(.testValue)]), 33 | (["BadRequest"], [.badRequest(.testValue)]), 34 | (["RequestInfo"], [.requestInfo(.testValue)]), 35 | (["ResourceInfo"], [.resourceInfo(.testValue)]), 36 | (["Help"], [.help(.testValue)]), 37 | (["LocalizedMessage"], [.localizedMessage(.testValue)]), 38 | (["DebugInfo", "RetryInfo"], [.debugInfo(.testValue), .retryInfo(.testValue)]), 39 | (["Help", "PreconditionFailure"], [.help(.testValue), .preconditionFailure(.testValue)]), 40 | (["Help", "Help", "Help"], [.help(.testValue), .help(.testValue), .help(.testValue)]), 41 | ] as [([String], [ErrorDetails])] 42 | ) 43 | @available(gRPCSwiftProtobuf 2.0, *) 44 | func rpcStatus(details: [String], expected: [ErrorDetails]) async throws { 45 | let inProcess = InProcessTransport() 46 | try await withGRPCServer(transport: inProcess.server, services: [ErrorThrowingService()]) { _ in 47 | try await withGRPCClient(transport: inProcess.client) { client in 48 | let errorClient = ErrorService.Client(wrapping: client) 49 | let subkinds = details.joined(separator: ",") 50 | let kind = "status/\(subkinds)" 51 | 52 | await #expect { 53 | try await errorClient.throwError(.with { $0.kind = kind }) 54 | } throws: { error in 55 | guard let rpcError = error as? RPCError else { return false } 56 | guard let status = try? rpcError.unpackGoogleRPCStatus() else { return false } 57 | 58 | // Code/message should be the same. 59 | #expect(status.code == rpcError.code) 60 | #expect(status.message == rpcError.message) 61 | 62 | // Set by the service. 63 | #expect(status.code == .unknown) 64 | #expect(status.message == subkinds) 65 | #expect(status.details == expected) 66 | 67 | return true 68 | } 69 | } 70 | } 71 | } 72 | 73 | @Test( 74 | arguments: [ 75 | (.errorInfo(.testValue), #"ErrorInfo(reason: "r", domain: "d", metadata: ["k": "v"])"#), 76 | (.retryInfo(.testValue), #"RetryInfo(delay: 1.0 seconds)"#), 77 | (.debugInfo(.testValue), #"DebugInfo(stack: ["foo.foo()", "foo.bar()"], detail: "detail")"#), 78 | ( 79 | .quotaFailure(.testValue), 80 | #"QuotaFailure(violations: [Violation(subject: "s", violationDescription: "d")])"# 81 | ), 82 | ( 83 | .preconditionFailure(.testValue), 84 | #"PreconditionFailure(violations: [Violation(subject: "s", type: "t", violationDescription: "d")])"# 85 | ), 86 | ( 87 | .badRequest(.testValue), 88 | #"BadRequest(violations: [FieldViolation(field: "f", violationDescription: "d")])"# 89 | ), 90 | (.requestInfo(.testValue), #"RequestInfo(requestID: "id", servingData: "d")"#), 91 | ( 92 | .resourceInfo(.testValue), 93 | #"ResourceInfo(name: "n", owner: "", type: "t", errorDescription: "d")"# 94 | ), 95 | (.help(.testValue), #"Help(links: [Link(url: "url", linkDescription: "d")])"#), 96 | (.localizedMessage(.testValue), #"LocalizedMessage(locale: "l", message: "m")"#), 97 | ] as [(ErrorDetails, String)] 98 | ) 99 | @available(gRPCSwiftProtobuf 2.0, *) 100 | func errorInfoDescription(_ details: ErrorDetails, expected: String) { 101 | #expect(String(describing: details) == expected) 102 | } 103 | 104 | @Test("Round-trip encoding of GoogleRPCStatus") 105 | @available(gRPCSwiftProtobuf 2.0, *) 106 | func googleRPCStatusRoundTripCoding() throws { 107 | let detail = ErrorDetails.BadRequest(violations: [.init(field: "foo", description: "bar")]) 108 | let status = GoogleRPCStatus(code: .dataLoss, message: "Uh oh", details: [.badRequest(detail)]) 109 | 110 | let serialized: [UInt8] = try status.serializedBytes() 111 | let deserialized = try GoogleRPCStatus(serializedBytes: serialized) 112 | #expect(deserialized.code == status.code) 113 | #expect(deserialized.message == status.message) 114 | #expect(deserialized.details.count == status.details.count) 115 | #expect(deserialized.details.first?.badRequest == detail) 116 | } 117 | } 118 | 119 | @available(gRPCSwiftProtobuf 2.0, *) 120 | private struct ErrorThrowingService: ErrorService.SimpleServiceProtocol { 121 | func throwError( 122 | request: ThrowInput, 123 | context: ServerContext 124 | ) async throws -> Google_Protobuf_Empty { 125 | if request.kind.starts(with: "status/") { 126 | try self.throwStatusError(kind: String(request.kind.dropFirst("status/".count))) 127 | } else { 128 | throw RPCError(code: .invalidArgument, message: "'\(request.kind)' is invalid.") 129 | } 130 | } 131 | 132 | private func throwStatusError(kind: String) throws(GoogleRPCStatus) -> Never { 133 | var details: [ErrorDetails] = [] 134 | for subkind in kind.split(separator: ",") { 135 | if let detail = self.errorDetails(kind: String(subkind)) { 136 | details.append(detail) 137 | } else { 138 | throw GoogleRPCStatus( 139 | code: .invalidArgument, 140 | message: "Unknown error subkind", 141 | details: [ 142 | .badRequest( 143 | violations: [ 144 | ErrorDetails.BadRequest.FieldViolation( 145 | field: "kind", 146 | description: "'\(kind)' is invalid" 147 | ) 148 | ] 149 | ) 150 | ] 151 | ) 152 | } 153 | } 154 | 155 | throw GoogleRPCStatus(code: .unknown, message: kind, details: details) 156 | } 157 | 158 | private func errorDetails(kind: String) -> ErrorDetails? { 159 | let details: ErrorDetails? 160 | 161 | switch kind { 162 | case "ErrorInfo": 163 | details = .errorInfo(.testValue) 164 | case "RetryInfo": 165 | details = .retryInfo(.testValue) 166 | case "DebugInfo": 167 | details = .debugInfo(.testValue) 168 | case "QuotaFailure": 169 | details = .quotaFailure(.testValue) 170 | case "PreconditionFailure": 171 | details = .preconditionFailure(.testValue) 172 | case "BadRequest": 173 | details = .badRequest(.testValue) 174 | case "RequestInfo": 175 | details = .requestInfo(.testValue) 176 | case "ResourceInfo": 177 | details = .resourceInfo(.testValue) 178 | case "Help": 179 | details = .help(.testValue) 180 | case "LocalizedMessage": 181 | details = .localizedMessage(.testValue) 182 | default: 183 | details = nil 184 | } 185 | 186 | return details 187 | } 188 | } 189 | 190 | @available(gRPCSwiftProtobuf 2.0, *) 191 | extension ErrorDetails.ErrorInfo { 192 | fileprivate static let testValue = Self(reason: "r", domain: "d", metadata: ["k": "v"]) 193 | } 194 | 195 | @available(gRPCSwiftProtobuf 2.0, *) 196 | extension ErrorDetails.RetryInfo { 197 | fileprivate static let testValue = Self(delay: .seconds(1)) 198 | } 199 | 200 | @available(gRPCSwiftProtobuf 2.0, *) 201 | extension ErrorDetails.DebugInfo { 202 | fileprivate static let testValue = Self( 203 | stack: ["foo.foo()", "foo.bar()"], 204 | detail: "detail" 205 | ) 206 | } 207 | 208 | @available(gRPCSwiftProtobuf 2.0, *) 209 | extension ErrorDetails.QuotaFailure { 210 | fileprivate static let testValue = Self( 211 | violations: [ 212 | Violation(subject: "s", description: "d") 213 | ] 214 | ) 215 | } 216 | 217 | @available(gRPCSwiftProtobuf 2.0, *) 218 | extension ErrorDetails.PreconditionFailure { 219 | fileprivate static let testValue = Self( 220 | violations: [ 221 | Violation(type: "t", subject: "s", description: "d") 222 | ] 223 | ) 224 | } 225 | 226 | @available(gRPCSwiftProtobuf 2.0, *) 227 | extension ErrorDetails.BadRequest { 228 | fileprivate static let testValue = Self( 229 | violations: [ 230 | FieldViolation(field: "f", description: "d") 231 | ] 232 | ) 233 | } 234 | 235 | @available(gRPCSwiftProtobuf 2.0, *) 236 | extension ErrorDetails.RequestInfo { 237 | fileprivate static let testValue = Self(requestID: "id", servingData: "d") 238 | } 239 | 240 | @available(gRPCSwiftProtobuf 2.0, *) 241 | extension ErrorDetails.ResourceInfo { 242 | fileprivate static let testValue = Self(type: "t", name: "n", errorDescription: "d") 243 | } 244 | 245 | @available(gRPCSwiftProtobuf 2.0, *) 246 | extension ErrorDetails.Help { 247 | fileprivate static let testValue = Self( 248 | links: [ 249 | Link(url: "url", description: "d") 250 | ] 251 | ) 252 | } 253 | 254 | @available(gRPCSwiftProtobuf 2.0, *) 255 | extension ErrorDetails.LocalizedMessage { 256 | fileprivate static let testValue = Self(locale: "l", message: "m") 257 | } 258 | -------------------------------------------------------------------------------- /Tests/GRPCProtobufTests/Errors/Generated/error-service.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // swift-format-ignore-file 3 | // swiftlint:disable all 4 | // 5 | // Generated by the Swift generator plugin for the protocol buffer compiler. 6 | // Source: error-service.proto 7 | // 8 | // For information on using the generated types, please see the documentation: 9 | // https://github.com/apple/swift-protobuf/ 10 | 11 | // 12 | // Copyright 2024, gRPC Authors All rights reserved. 13 | // 14 | // Licensed under the Apache License, Version 2.0 (the "License"); 15 | // you may not use this file except in compliance with the License. 16 | // You may obtain a copy of the License at 17 | // 18 | // http://www.apache.org/licenses/LICENSE-2.0 19 | // 20 | // Unless required by applicable law or agreed to in writing, software 21 | // distributed under the License is distributed on an "AS IS" BASIS, 22 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | // See the License for the specific language governing permissions and 24 | // limitations under the License. 25 | 26 | internal import SwiftProtobuf 27 | 28 | // If the compiler emits an error on this type, it is because this file 29 | // was generated by a version of the `protoc` Swift plug-in that is 30 | // incompatible with the version of SwiftProtobuf to which you are linking. 31 | // Please ensure that you are building against the same version of the API 32 | // that was used to generate this file. 33 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 34 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 35 | typealias Version = _2 36 | } 37 | 38 | struct ThrowInput: Sendable { 39 | // SwiftProtobuf.Message conformance is added in an extension below. See the 40 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 41 | // methods supported on all messages. 42 | 43 | var kind: String = String() 44 | 45 | var unknownFields = SwiftProtobuf.UnknownStorage() 46 | 47 | init() {} 48 | } 49 | 50 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 51 | 52 | extension ThrowInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 53 | static let protoMessageName: String = "ThrowInput" 54 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 55 | 1: .same(proto: "kind"), 56 | ] 57 | 58 | mutating func decodeMessage(decoder: inout D) throws { 59 | while let fieldNumber = try decoder.nextFieldNumber() { 60 | // The use of inline closures is to circumvent an issue where the compiler 61 | // allocates stack space for every case branch when no optimizations are 62 | // enabled. https://github.com/apple/swift-protobuf/issues/1034 63 | switch fieldNumber { 64 | case 1: try { try decoder.decodeSingularStringField(value: &self.kind) }() 65 | default: break 66 | } 67 | } 68 | } 69 | 70 | func traverse(visitor: inout V) throws { 71 | if !self.kind.isEmpty { 72 | try visitor.visitSingularStringField(value: self.kind, fieldNumber: 1) 73 | } 74 | try unknownFields.traverse(visitor: &visitor) 75 | } 76 | 77 | static func ==(lhs: ThrowInput, rhs: ThrowInput) -> Bool { 78 | if lhs.kind != rhs.kind {return false} 79 | if lhs.unknownFields != rhs.unknownFields {return false} 80 | return true 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/GRPCProtobufTests/ProtobufCodingTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | import GRPCCore 18 | import GRPCProtobuf 19 | import SwiftProtobuf 20 | import XCTest 21 | 22 | @available(gRPCSwiftProtobuf 2.0, *) 23 | final class ProtobufCodingTests: XCTestCase { 24 | func testSerializeDeserializeRoundtrip() throws { 25 | let message = Google_Protobuf_Timestamp.with { 26 | $0.seconds = 4 27 | } 28 | 29 | let serializer = ProtobufSerializer() 30 | let deserializer = ProtobufDeserializer() 31 | 32 | let bytes = try serializer.serialize(message) as [UInt8] 33 | let roundTrip = try deserializer.deserialize(bytes) 34 | XCTAssertEqual(roundTrip, message) 35 | } 36 | 37 | func testSerializerError() throws { 38 | let message = TestMessage() 39 | let serializer = ProtobufSerializer() 40 | 41 | XCTAssertThrowsError( 42 | try serializer.serialize(message) as [UInt8] 43 | ) { error in 44 | XCTAssertEqual( 45 | error as? RPCError, 46 | RPCError( 47 | code: .invalidArgument, 48 | message: 49 | """ 50 | Can't serialize message of type TestMessage. 51 | """ 52 | ) 53 | ) 54 | } 55 | } 56 | 57 | func testDeserializerError() throws { 58 | let bytes = Array("%%%%%££££".utf8) 59 | let deserializer = ProtobufDeserializer() 60 | XCTAssertThrowsError( 61 | try deserializer.deserialize(bytes) 62 | ) { error in 63 | XCTAssertEqual( 64 | error as? RPCError, 65 | RPCError( 66 | code: .invalidArgument, 67 | message: 68 | """ 69 | Can't deserialize to message of type TestMessage. 70 | """ 71 | ) 72 | ) 73 | } 74 | } 75 | } 76 | 77 | @available(gRPCSwiftProtobuf 2.0, *) 78 | struct TestMessage: SwiftProtobuf.Message { 79 | var text: String = "" 80 | var unknownFields = SwiftProtobuf.UnknownStorage() 81 | static let protoMessageName: String = "Test.ServiceRequest" 82 | init() {} 83 | 84 | mutating func decodeMessage(decoder: inout D) throws where D: SwiftProtobuf.Decoder { 85 | throw RPCError(code: .internalError, message: "Decoding error") 86 | } 87 | 88 | func traverse(visitor: inout V) throws where V: SwiftProtobuf.Visitor { 89 | throw RPCError(code: .internalError, message: "Traversing error") 90 | } 91 | 92 | public var isInitialized: Bool { 93 | if self.text.isEmpty { return false } 94 | return true 95 | } 96 | 97 | func isEqualTo(message: any SwiftProtobuf.Message) -> Bool { 98 | return false 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /dev/check-generated-code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -euo pipefail 17 | 18 | log() { printf -- "** %s\n" "$*" >&2; } 19 | error() { printf -- "** ERROR: %s\n" "$*" >&2; } 20 | fatal() { error "$@"; exit 1; } 21 | 22 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 23 | 24 | # Re-generate everything. 25 | log "Regenerating protos..." 26 | "$here"/protos/generate.sh 27 | 28 | # Check for changes. 29 | GIT_PAGER='' git diff --exit-code '*.swift' 30 | 31 | log "Generated code is up-to-date" 32 | -------------------------------------------------------------------------------- /dev/execute-plugin-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -euo pipefail 17 | 18 | log() { printf -- "** %s\n" "$*" >&2; } 19 | error() { printf -- "** ERROR: %s\n" "$*" >&2; } 20 | fatal() { error "$@"; exit 1; } 21 | 22 | tests_directory="${PLUGIN_TESTS_DIRECTORY:=""}" 23 | if [[ -z "$tests_directory" ]]; then 24 | fatal "Tests parent directory must be specified." 25 | fi 26 | 27 | for dir in "$tests_directory"/test_*/ ; do 28 | if [[ -f "$dir/Package.swift" ]]; then 29 | plugin_test=$(basename "$dir") 30 | log "Building '$plugin_test' plugin test" 31 | 32 | if ! build_output=$(swift build --package-path "$dir" 2>&1); then 33 | # Only print the build output on failure. 34 | echo "$build_output" 35 | fatal "Build failed for '$plugin_test'" 36 | else 37 | log "Build succeeded for '$plugin_test'" 38 | fi 39 | fi 40 | done 41 | -------------------------------------------------------------------------------- /dev/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2020, gRPC Authors All rights reserved. 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 | set -eu 17 | 18 | function log() { printf -- "** %s\n" "$*" >&2; } 19 | function error() { printf -- "** ERROR: %s\n" "$*" >&2; } 20 | function fatal() { error "$*"; exit 1; } 21 | 22 | function usage() { 23 | echo >&2 "Usage:" 24 | echo >&2 " $0 -[f|l]" 25 | echo >&2 "" 26 | echo >&2 "Options:" 27 | echo >&2 " -f Format source code in place (default)" 28 | echo >&2 " -l Lint check without formatting the source code" 29 | } 30 | 31 | format=true 32 | lint=false 33 | while getopts ":flh" opt; do 34 | case "$opt" in 35 | f) 36 | format=true 37 | lint=false 38 | ;; 39 | l) 40 | format=false 41 | lint=true 42 | ;; 43 | h) 44 | usage 45 | exit 1 46 | ;; 47 | \?) 48 | usage 49 | exit 1 50 | ;; 51 | esac 52 | done 53 | 54 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 55 | repo="$here/.." 56 | 57 | if "$lint"; then 58 | swift format lint \ 59 | --parallel --recursive --strict \ 60 | "${repo}/Sources" \ 61 | "${repo}/Tests" \ 62 | "${repo}/Plugins" \ 63 | && SWIFT_FORMAT_RC=$? || SWIFT_FORMAT_RC=$? 64 | 65 | if [[ "${SWIFT_FORMAT_RC}" -ne 0 ]]; then 66 | fatal "Running swift format produced errors. 67 | 68 | To fix, run the following command: 69 | 70 | % $THIS_SCRIPT -f 71 | " "${SWIFT_FORMAT_RC}" 72 | fi 73 | 74 | log "Ran swift format lint with no errors." 75 | elif "$format"; then 76 | swift format \ 77 | --parallel --recursive --in-place \ 78 | "${repo}/Sources" \ 79 | "${repo}/Tests" \ 80 | "${repo}/Plugins" \ 81 | && SWIFT_FORMAT_RC=$? || SWIFT_FORMAT_RC=$? 82 | 83 | if [[ "${SWIFT_FORMAT_RC}" -ne 0 ]]; then 84 | fatal "Running swift format produced errors." "${SWIFT_FORMAT_RC}" 85 | fi 86 | 87 | log "Ran swift format with no errors." 88 | else 89 | fatal "No actions taken." 90 | fi 91 | -------------------------------------------------------------------------------- /dev/git.commit.template: -------------------------------------------------------------------------------- 1 | One line description of your change 2 | 3 | Motivation: 4 | 5 | Explain here the context, and why you're making that change. 6 | What is the problem you're trying to solve. 7 | 8 | Modifications: 9 | 10 | Describe the modifications you've done. 11 | 12 | Result: 13 | 14 | After your change, what will change. 15 | -------------------------------------------------------------------------------- /dev/license-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2019, gRPC Authors All rights reserved. 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 script checks the copyright headers in source *.swift source files and 17 | # exits if they do not match the expected header. The year, or year range in 18 | # headers is replaced with 'YEARS' for comparison. 19 | 20 | # Copyright header text and SHA for *.swift files 21 | read -r -d '' COPYRIGHT_HEADER_SWIFT << 'EOF' 22 | /* 23 | * Copyright YEARS, gRPC Authors All rights reserved. 24 | * 25 | * Licensed under the Apache License, Version 2.0 (the "License"); 26 | * you may not use this file except in compliance with the License. 27 | * You may obtain a copy of the License at 28 | * 29 | * http://www.apache.org/licenses/LICENSE-2.0 30 | * 31 | * Unless required by applicable law or agreed to in writing, software 32 | * distributed under the License is distributed on an "AS IS" BASIS, 33 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | * See the License for the specific language governing permissions and 35 | * limitations under the License. 36 | */ 37 | EOF 38 | SWIFT_SHA=$(echo "$COPYRIGHT_HEADER_SWIFT" | shasum | awk '{print $1}') 39 | 40 | # Checks the Copyright headers for *.swift files in this repository against the 41 | # expected headers. 42 | # 43 | # Prints the names of all files with invalid or missing headers and exits with 44 | # a non-zero status code. 45 | check_copyright_headers() { 46 | # Exceptions: 47 | # - {echo,annotations,language_service,http}.pb.swift: Google is the author of 48 | # the corresponding .protos, so the generated header has Google as the 49 | # author. 50 | # - LinuxMain.swift, XCTestManifests.swift: Both of these files are generated 51 | # by SwiftPM and do not have headers. 52 | while read -r filename; do 53 | case $filename in 54 | # The .grpc.swift and .pb.swift files have additional generated headers with 55 | # warnings that they have been generated and should not be edited. 56 | # Package.swift is preceeded by a "swift-tools-version" line. 57 | *.grpc.swift) 58 | expected_sha="$SWIFT_GRPC_SHA" 59 | drop_first=9 60 | expected_lines=13 61 | ;; 62 | *.pb.swift) 63 | expected_sha="$SWIFT_GRPC_PB" 64 | drop_first=9 65 | expected_lines=13 66 | ;; 67 | */Package.swift) 68 | expected_sha="$SWIFT_SHA" 69 | drop_first=1 70 | expected_lines=15 71 | ;; 72 | */Package@swift-*.*.swift) 73 | expected_sha="$SWIFT_SHA" 74 | drop_first=1 75 | expected_lines=15 76 | ;; 77 | */Package@swift-*.swift) 78 | expected_sha="$SWIFT_SHA" 79 | drop_first=1 80 | expected_lines=15 81 | ;; 82 | *) 83 | expected_sha="$SWIFT_SHA" 84 | drop_first=0 85 | expected_lines=15 86 | ;; 87 | esac 88 | 89 | actual_sha=$(head -n "$((drop_first + expected_lines))" "$filename" \ 90 | | tail -n "$expected_lines" \ 91 | | sed -e 's/201[56789]-20[12][0-9]/YEARS/' -e 's/20[12][0-9]/YEARS/' \ 92 | | shasum \ 93 | | awk '{print $1}') 94 | 95 | if [ "$actual_sha" != "$expected_sha" ]; then 96 | printf "\033[0;31mMissing or invalid copyright headers in '%s'\033[0m\n" "$filename" 97 | errors=$(( errors + 1 )) 98 | fi 99 | 100 | done < <(find . -name '*.swift' \ 101 | ! -name '*.pb.swift' \ 102 | ! -name '*.grpc.swift' \ 103 | ! -path './.build/*') 104 | } 105 | 106 | errors=0 107 | check_copyright_headers 108 | exit $errors 109 | -------------------------------------------------------------------------------- /dev/plugin-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -euo pipefail 17 | 18 | log() { printf -- "** %s\n" "$*" >&2; } 19 | error() { printf -- "** ERROR: %s\n" "$*" >&2; } 20 | fatal() { error "$@"; exit 1; } 21 | 22 | if [[ -n ${GITHUB_ACTIONS:=""} ]]; then 23 | # we will have been piped to bash and won't know the location of the script 24 | echo "Running in GitHub Actions" 25 | source_directory="$(pwd)" 26 | else 27 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 28 | source_directory="$(readlink -f "${here}/..")" 29 | fi 30 | tests_directory="${PLUGIN_TESTS_OUTPUT_DIRECTORY:=$(mktemp -d)}" 31 | 32 | PLUGIN_TESTS_OUTPUT_DIRECTORY="$tests_directory" "${source_directory}/dev/setup-plugin-tests.sh" 33 | PLUGIN_TESTS_DIRECTORY="$tests_directory" "${source_directory}/dev/execute-plugin-tests.sh" 34 | -------------------------------------------------------------------------------- /dev/protos/fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -eu 17 | 18 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | upstream="$here/upstream" 20 | 21 | # Create a temporary directory for the repo checkouts. 22 | checkouts="$(mktemp -d)" 23 | 24 | # Clone the google protos into the staging area. 25 | git clone --depth 1 https://github.com/googleapis/googleapis.git "$checkouts/googleapis" 26 | 27 | # Remove the old protos. 28 | rm -rf "$upstream" 29 | 30 | # Copy over the googleapis protos. 31 | mkdir -p "$upstream/google/rpc" 32 | cp -rp "$checkouts/googleapis/google/rpc/error_details.proto" "$upstream/google/rpc/error_details.proto" 33 | cp -rp "$checkouts/googleapis/google/rpc/code.proto" "$upstream/google/rpc/code.proto" 34 | cp -rp "$checkouts/googleapis/google/rpc/status.proto" "$upstream/google/rpc/status.proto" 35 | -------------------------------------------------------------------------------- /dev/protos/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -eu 17 | 18 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | root="$here/../.." 20 | protoc=$(which protoc) 21 | 22 | # Checkout and build the plugins. 23 | swift build --package-path "$root" --product protoc-gen-swift 24 | swift build --package-path "$root" --product protoc-gen-grpc-swift-2 25 | 26 | # Grab the plugin paths. 27 | bin_path=$(swift build --package-path "$root" --show-bin-path) 28 | protoc_gen_swift="$bin_path/protoc-gen-swift" 29 | protoc_gen_grpc_swift="$bin_path/protoc-gen-grpc-swift-2" 30 | 31 | # Generates gRPC by invoking protoc with the gRPC Swift plugin. 32 | # Parameters: 33 | # - $1: .proto file 34 | # - $2: proto path 35 | # - $3: output path 36 | # - $4 onwards: options to forward to the plugin 37 | function generate_grpc { 38 | local proto=$1 39 | local args=("--plugin=$protoc_gen_grpc_swift" "--proto_path=${2}" "--grpc-swift-2_out=${3}") 40 | 41 | for option in "${@:4}"; do 42 | args+=("--grpc-swift-2_opt=$option") 43 | done 44 | 45 | invoke_protoc "${args[@]}" "$proto" 46 | } 47 | 48 | # Generates messages by invoking protoc with the Swift plugin. 49 | # Parameters: 50 | # - $1: .proto file 51 | # - $2: proto path 52 | # - $3: output path 53 | # - $4 onwards: options to forward to the plugin 54 | function generate_message { 55 | local proto=$1 56 | local args=("--plugin=$protoc_gen_swift" "--proto_path=$2" "--swift_out=$3") 57 | 58 | for option in "${@:4}"; do 59 | args+=("--swift_opt=$option") 60 | done 61 | 62 | invoke_protoc "${args[@]}" "$proto" 63 | } 64 | 65 | function invoke_protoc { 66 | # Setting -x when running the script produces a lot of output, instead boil 67 | # just echo out the protoc invocations. 68 | echo "$protoc" "$@" 69 | "$protoc" "$@" 70 | } 71 | 72 | #- DETAILED ERROR ------------------------------------------------------------- 73 | 74 | function generate_rpc_error_details { 75 | local protos output 76 | 77 | protos=( 78 | "$here/upstream/google/rpc/status.proto" 79 | "$here/upstream/google/rpc/code.proto" 80 | "$here/upstream/google/rpc/error_details.proto" 81 | ) 82 | output="$root/Sources/GRPCProtobuf/Errors/Generated" 83 | 84 | for proto in "${protos[@]}"; do 85 | generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true" 86 | done 87 | } 88 | 89 | #- DETAILED ERROR Tests ------------------------------------------------------- 90 | 91 | function generate_error_service { 92 | local proto output 93 | proto="$here/local/error-service.proto" 94 | output="$root/Tests/GRPCProtobufTests/Errors/Generated" 95 | 96 | generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true" 97 | generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "UseAccessLevelOnImports=true" "Availability=gRPCSwiftProtobuf 2.0" 98 | } 99 | 100 | #- DESCRIPTOR SETS ------------------------------------------------------------ 101 | 102 | function generate_test_service_descriptor_set { 103 | local proto proto_path output 104 | proto="$here/local/test-service.proto" 105 | proto_path="$(dirname "$proto")" 106 | output="$root/Tests/GRPCProtobufCodeGenTests/Generated/test-service.pb" 107 | 108 | invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \ 109 | --include_imports \ 110 | --include_source_info 111 | } 112 | 113 | function generate_foo_service_descriptor_set { 114 | local proto proto_path output 115 | proto="$here/local/foo-service.proto" 116 | proto_path="$(dirname "$proto")" 117 | output="$root/Tests/GRPCProtobufCodeGenTests/Generated/foo-service.pb" 118 | 119 | invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \ 120 | --include_source_info \ 121 | --include_imports 122 | } 123 | 124 | function generate_foo_messages_descriptor_set { 125 | local proto proto_path output 126 | proto="$here/local/foo-messages.proto" 127 | proto_path="$(dirname "$proto")" 128 | output="$root/Tests/GRPCProtobufCodeGenTests/Generated/foo-messages.pb" 129 | 130 | invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \ 131 | --include_source_info \ 132 | --include_imports 133 | } 134 | 135 | function generate_bar_service_descriptor_set { 136 | local proto proto_path output 137 | proto="$here/local/bar-service.proto" 138 | proto_path="$(dirname "$proto")" 139 | output="$root/Tests/GRPCProtobufCodeGenTests/Generated/bar-service.pb" 140 | 141 | invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \ 142 | --include_source_info \ 143 | --include_imports 144 | } 145 | 146 | function generate_wkt_service_descriptor_set { 147 | local proto proto_path output 148 | proto="$here/local/wkt-service.proto" 149 | proto_path="$(dirname "$proto")" 150 | output="$root/Tests/GRPCProtobufCodeGenTests/Generated/wkt-service.pb" 151 | 152 | invoke_protoc --descriptor_set_out="$output" "$proto" -I "$proto_path" \ 153 | --include_source_info \ 154 | --include_imports 155 | } 156 | 157 | #------------------------------------------------------------------------------ 158 | 159 | # Detailed error model 160 | generate_rpc_error_details 161 | 162 | # Service for testing error details 163 | generate_error_service 164 | 165 | # Descriptor sets for tests 166 | generate_test_service_descriptor_set 167 | generate_foo_service_descriptor_set 168 | generate_foo_messages_descriptor_set 169 | generate_bar_service_descriptor_set 170 | generate_wkt_service_descriptor_set 171 | -------------------------------------------------------------------------------- /dev/protos/local/bar-service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service BarService {} 4 | -------------------------------------------------------------------------------- /dev/protos/local/error-service.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, gRPC Authors All rights reserved. 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 | 17 | syntax = "proto3"; 18 | 19 | import "google/protobuf/empty.proto"; 20 | 21 | service ErrorService { 22 | rpc ThrowError (ThrowInput) returns (google.protobuf.Empty) {} 23 | } 24 | 25 | message ThrowInput { 26 | string kind = 1; 27 | } 28 | -------------------------------------------------------------------------------- /dev/protos/local/foo-messages.proto: -------------------------------------------------------------------------------- 1 | // Leading trivia. 2 | syntax = "proto3"; 3 | 4 | package foo; 5 | 6 | message FooInput {} 7 | message FooOutput {} 8 | -------------------------------------------------------------------------------- /dev/protos/local/foo-service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "foo-messages.proto"; 4 | 5 | package foo; 6 | 7 | service FooService1 { 8 | rpc Foo (FooInput) returns (FooOutput) {} 9 | } 10 | 11 | service FooService2 { 12 | rpc Foo (FooInput) returns (FooOutput) {} 13 | } 14 | -------------------------------------------------------------------------------- /dev/protos/local/test-service.proto: -------------------------------------------------------------------------------- 1 | // Leading trivia. 2 | syntax = "proto3"; 3 | 4 | package test; 5 | 6 | // Using a WKT forces an "SwiftProtobuf" to be imported in generated code. 7 | import "google/protobuf/any.proto"; 8 | 9 | // Service docs. 10 | service TestService { 11 | // Unary docs. 12 | rpc Unary (TestInput) returns (TestOutput) {} 13 | // Client streaming docs. 14 | rpc ClientStreaming (stream TestInput) returns (TestOutput) {} 15 | // Server streaming docs. 16 | rpc ServerStreaming (TestInput) returns (stream TestOutput) {} 17 | // Bidirectional streaming docs. 18 | rpc BidirectionalStreaming (stream TestInput) returns (stream TestOutput) {} 19 | } 20 | 21 | message TestInput { 22 | google.protobuf.Any any = 1; 23 | } 24 | 25 | message TestOutput {} 26 | -------------------------------------------------------------------------------- /dev/protos/local/wkt-service.proto: -------------------------------------------------------------------------------- 1 | // Leading trivia. 2 | syntax = "proto3"; 3 | 4 | package test; 5 | 6 | import "google/protobuf/any.proto"; 7 | 8 | service WKTService { 9 | rpc Method (google.protobuf.Any) returns (google.protobuf.Any) {} 10 | } 11 | -------------------------------------------------------------------------------- /dev/protos/upstream/google/rpc/code.proto: -------------------------------------------------------------------------------- 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 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; 20 | option java_multiple_files = true; 21 | option java_outer_classname = "CodeProto"; 22 | option java_package = "com.google.rpc"; 23 | option objc_class_prefix = "RPC"; 24 | 25 | // The canonical error codes for gRPC APIs. 26 | // 27 | // 28 | // Sometimes multiple error codes may apply. Services should return 29 | // the most specific error code that applies. For example, prefer 30 | // `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. 31 | // Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. 32 | enum Code { 33 | // Not an error; returned on success. 34 | // 35 | // HTTP Mapping: 200 OK 36 | OK = 0; 37 | 38 | // The operation was cancelled, typically by the caller. 39 | // 40 | // HTTP Mapping: 499 Client Closed Request 41 | CANCELLED = 1; 42 | 43 | // Unknown error. For example, this error may be returned when 44 | // a `Status` value received from another address space belongs to 45 | // an error space that is not known in this address space. Also 46 | // errors raised by APIs that do not return enough error information 47 | // may be converted to this error. 48 | // 49 | // HTTP Mapping: 500 Internal Server Error 50 | UNKNOWN = 2; 51 | 52 | // The client specified an invalid argument. Note that this differs 53 | // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments 54 | // that are problematic regardless of the state of the system 55 | // (e.g., a malformed file name). 56 | // 57 | // HTTP Mapping: 400 Bad Request 58 | INVALID_ARGUMENT = 3; 59 | 60 | // The deadline expired before the operation could complete. For operations 61 | // that change the state of the system, this error may be returned 62 | // even if the operation has completed successfully. For example, a 63 | // successful response from a server could have been delayed long 64 | // enough for the deadline to expire. 65 | // 66 | // HTTP Mapping: 504 Gateway Timeout 67 | DEADLINE_EXCEEDED = 4; 68 | 69 | // Some requested entity (e.g., file or directory) was not found. 70 | // 71 | // Note to server developers: if a request is denied for an entire class 72 | // of users, such as gradual feature rollout or undocumented allowlist, 73 | // `NOT_FOUND` may be used. If a request is denied for some users within 74 | // a class of users, such as user-based access control, `PERMISSION_DENIED` 75 | // must be used. 76 | // 77 | // HTTP Mapping: 404 Not Found 78 | NOT_FOUND = 5; 79 | 80 | // The entity that a client attempted to create (e.g., file or directory) 81 | // already exists. 82 | // 83 | // HTTP Mapping: 409 Conflict 84 | ALREADY_EXISTS = 6; 85 | 86 | // The caller does not have permission to execute the specified 87 | // operation. `PERMISSION_DENIED` must not be used for rejections 88 | // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` 89 | // instead for those errors). `PERMISSION_DENIED` must not be 90 | // used if the caller can not be identified (use `UNAUTHENTICATED` 91 | // instead for those errors). This error code does not imply the 92 | // request is valid or the requested entity exists or satisfies 93 | // other pre-conditions. 94 | // 95 | // HTTP Mapping: 403 Forbidden 96 | PERMISSION_DENIED = 7; 97 | 98 | // The request does not have valid authentication credentials for the 99 | // operation. 100 | // 101 | // HTTP Mapping: 401 Unauthorized 102 | UNAUTHENTICATED = 16; 103 | 104 | // Some resource has been exhausted, perhaps a per-user quota, or 105 | // perhaps the entire file system is out of space. 106 | // 107 | // HTTP Mapping: 429 Too Many Requests 108 | RESOURCE_EXHAUSTED = 8; 109 | 110 | // The operation was rejected because the system is not in a state 111 | // required for the operation's execution. For example, the directory 112 | // to be deleted is non-empty, an rmdir operation is applied to 113 | // a non-directory, etc. 114 | // 115 | // Service implementors can use the following guidelines to decide 116 | // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: 117 | // (a) Use `UNAVAILABLE` if the client can retry just the failing call. 118 | // (b) Use `ABORTED` if the client should retry at a higher level. For 119 | // example, when a client-specified test-and-set fails, indicating the 120 | // client should restart a read-modify-write sequence. 121 | // (c) Use `FAILED_PRECONDITION` if the client should not retry until 122 | // the system state has been explicitly fixed. For example, if an "rmdir" 123 | // fails because the directory is non-empty, `FAILED_PRECONDITION` 124 | // should be returned since the client should not retry unless 125 | // the files are deleted from the directory. 126 | // 127 | // HTTP Mapping: 400 Bad Request 128 | FAILED_PRECONDITION = 9; 129 | 130 | // The operation was aborted, typically due to a concurrency issue such as 131 | // a sequencer check failure or transaction abort. 132 | // 133 | // See the guidelines above for deciding between `FAILED_PRECONDITION`, 134 | // `ABORTED`, and `UNAVAILABLE`. 135 | // 136 | // HTTP Mapping: 409 Conflict 137 | ABORTED = 10; 138 | 139 | // The operation was attempted past the valid range. E.g., seeking or 140 | // reading past end-of-file. 141 | // 142 | // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may 143 | // be fixed if the system state changes. For example, a 32-bit file 144 | // system will generate `INVALID_ARGUMENT` if asked to read at an 145 | // offset that is not in the range [0,2^32-1], but it will generate 146 | // `OUT_OF_RANGE` if asked to read from an offset past the current 147 | // file size. 148 | // 149 | // There is a fair bit of overlap between `FAILED_PRECONDITION` and 150 | // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific 151 | // error) when it applies so that callers who are iterating through 152 | // a space can easily look for an `OUT_OF_RANGE` error to detect when 153 | // they are done. 154 | // 155 | // HTTP Mapping: 400 Bad Request 156 | OUT_OF_RANGE = 11; 157 | 158 | // The operation is not implemented or is not supported/enabled in this 159 | // service. 160 | // 161 | // HTTP Mapping: 501 Not Implemented 162 | UNIMPLEMENTED = 12; 163 | 164 | // Internal errors. This means that some invariants expected by the 165 | // underlying system have been broken. This error code is reserved 166 | // for serious errors. 167 | // 168 | // HTTP Mapping: 500 Internal Server Error 169 | INTERNAL = 13; 170 | 171 | // The service is currently unavailable. This is most likely a 172 | // transient condition, which can be corrected by retrying with 173 | // a backoff. Note that it is not always safe to retry 174 | // non-idempotent operations. 175 | // 176 | // See the guidelines above for deciding between `FAILED_PRECONDITION`, 177 | // `ABORTED`, and `UNAVAILABLE`. 178 | // 179 | // HTTP Mapping: 503 Service Unavailable 180 | UNAVAILABLE = 14; 181 | 182 | // Unrecoverable data loss or corruption. 183 | // 184 | // HTTP Mapping: 500 Internal Server Error 185 | DATA_LOSS = 15; 186 | } 187 | -------------------------------------------------------------------------------- /dev/protos/upstream/google/rpc/status.proto: -------------------------------------------------------------------------------- 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 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "StatusProto"; 25 | option java_package = "com.google.rpc"; 26 | option objc_class_prefix = "RPC"; 27 | 28 | // The `Status` type defines a logical error model that is suitable for 29 | // different programming environments, including REST APIs and RPC APIs. It is 30 | // used by [gRPC](https://github.com/grpc). Each `Status` message contains 31 | // three pieces of data: error code, error message, and error details. 32 | // 33 | // You can find out more about this error model and how to work with it in the 34 | // [API Design Guide](https://cloud.google.com/apis/design/errors). 35 | message Status { 36 | // The status code, which should be an enum value of 37 | // [google.rpc.Code][google.rpc.Code]. 38 | int32 code = 1; 39 | 40 | // A developer-facing error message, which should be in English. Any 41 | // user-facing error message should be localized and sent in the 42 | // [google.rpc.Status.details][google.rpc.Status.details] field, or localized 43 | // by the client. 44 | string message = 2; 45 | 46 | // A list of messages that carry the error details. There is a common set of 47 | // message types for APIs to use. 48 | repeated google.protobuf.Any details = 3; 49 | } 50 | -------------------------------------------------------------------------------- /dev/setup-plugin-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024, gRPC Authors All rights reserved. 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 | set -euo pipefail 17 | 18 | log() { printf -- "** %s\n" "$*" >&2; } 19 | error() { printf -- "** ERROR: %s\n" "$*" >&2; } 20 | fatal() { error "$@"; exit 1; } 21 | 22 | output_directory="${PLUGIN_TESTS_OUTPUT_DIRECTORY:=$(mktemp -d)}" 23 | 24 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 25 | grpc_swift_protobuf_directory="$(readlink -f "${here}/..")" 26 | resources_directory="$(readlink -f "${grpc_swift_protobuf_directory}/IntegrationTests/PluginTests/Resources")" 27 | config="${resources_directory}/Config" 28 | sources="${resources_directory}/Sources" 29 | protos="${resources_directory}/Protos" 30 | scratch_directory="$(mktemp -d)" 31 | package_manifest="${scratch_directory}/Package.swift" 32 | 33 | echo "Output directory: $output_directory" 34 | echo "grpc-swift-protobuf directory: $grpc_swift_protobuf_directory" 35 | 36 | # modify Package.swift 37 | cp "${resources_directory}/Sources/Package.swift" "${scratch_directory}/" 38 | cat >> "${package_manifest}" <<- EOM 39 | package.dependencies.append( 40 | .package(path: "$grpc_swift_protobuf_directory") 41 | ) 42 | EOM 43 | 44 | function test_dir_name { 45 | # $FUNCNAME is a stack of function names. The 0th element is the name of this 46 | # function, so the 1st element is the calling function. 47 | echo "${output_directory}/${FUNCNAME[1]}" 48 | } 49 | 50 | function test_01_top_level_config_file { 51 | # . 52 | # ├── Package.swift 53 | # └── Sources 54 | # ├── HelloWorldAdopter.swift 55 | # ├── Protos 56 | # │ └── HelloWorld.proto 57 | # └── grpc-swift-proto-generator-config.json 58 | 59 | local -r test_dir=$(test_dir_name) 60 | mkdir -p "${test_dir}/Sources/Protos" 61 | cp "${package_manifest}" "${test_dir}/" 62 | cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/" 63 | cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos" 64 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/grpc-swift-proto-generator-config.json" 65 | } 66 | 67 | function test_02_peer_config_file { 68 | # . 69 | # ├── Package.swift 70 | # └── Sources 71 | # ├── HelloWorldAdopter.swift 72 | # └── Protos 73 | # ├── HelloWorld.proto 74 | # └── grpc-swift-proto-generator-config.json 75 | 76 | local -r test_dir=$(test_dir_name) 77 | mkdir -p "${test_dir}/Sources/Protos" 78 | cp "${package_manifest}" "${test_dir}/" 79 | cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/" 80 | cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/" 81 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json" 82 | } 83 | 84 | function test_03_separate_service_message_protos { 85 | # . 86 | # ├── Package.swift 87 | # └── Sources 88 | # ├── HelloWorldAdopter.swift 89 | # └── Protos 90 | # ├── Messages.proto 91 | # ├── Service.proto 92 | # └── grpc-swift-proto-generator-config.json 93 | 94 | local -r test_dir=$(test_dir_name) 95 | mkdir -p "${test_dir}/Sources/Protos" 96 | cp "${package_manifest}" "${test_dir}/" 97 | cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/" 98 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json" 99 | cp "${protos}/HelloWorld/Service.proto" "${test_dir}/Sources/Protos/" 100 | cp "${protos}/HelloWorld/Messages.proto" "${test_dir}/Sources/Protos/" 101 | } 102 | 103 | function test_04_cross_directory_imports { 104 | # . 105 | # ├── Package.swift 106 | # └── Sources 107 | # ├── HelloWorldAdopter.swift 108 | # └── Protos 109 | # ├── directory_1 110 | # │ ├── Messages.proto 111 | # │ └── grpc-swift-proto-generator-config.json 112 | # └── directory_2 113 | # ├── Service.proto 114 | # └── grpc-swift-proto-generator-config.json 115 | 116 | local -r test_dir=$(test_dir_name) 117 | mkdir -p "${test_dir}/Sources/Protos/directory_1" 118 | mkdir -p "${test_dir}/Sources/Protos/directory_2" 119 | 120 | cp "${package_manifest}" "${test_dir}/" 121 | cp "${sources}/HelloWorldAdopter.swift" "${test_dir}/Sources/" 122 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/directory_1/grpc-swift-proto-generator-config.json" 123 | cp "${config}/import-directory-1-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/directory_2/grpc-swift-proto-generator-config.json" 124 | cp "${protos}/HelloWorld/Service.proto" "${test_dir}/Sources/Protos/directory_2/" 125 | cp "${protos}/HelloWorld/Messages.proto" "${test_dir}/Sources/Protos/directory_1/" 126 | } 127 | 128 | function test_05_two_definitions { 129 | # . 130 | # ├── Package.swift 131 | # └── Sources 132 | # ├── FooHelloWorldAdopter.swift 133 | # └── Protos 134 | # ├── Foo 135 | # │ ├── foo-messages.proto 136 | # │ └── foo-service.proto 137 | # ├── HelloWorld 138 | # │ └── HelloWorld.proto 139 | # └── grpc-swift-proto-generator-config.json 140 | 141 | local -r test_dir=$(test_dir_name) 142 | mkdir -p "${test_dir}/Sources/Protos/HelloWorld" 143 | mkdir -p "${test_dir}/Sources/Protos/Foo" 144 | 145 | cp "${package_manifest}" "${test_dir}/" 146 | cp "${sources}/FooHelloWorldAdopter.swift" "${test_dir}/Sources/" 147 | cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/HelloWorld/" 148 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json" 149 | cp "${protos}/Foo/foo-messages.proto" "${test_dir}/Sources/Protos/Foo/" 150 | cp "${protos}/Foo/foo-service.proto" "${test_dir}/Sources/Protos/Foo/" 151 | } 152 | 153 | function test_06_nested_definitions { 154 | # . 155 | # ├── Package.swift 156 | # └── Sources 157 | # ├── FooHelloWorldAdopter.swift 158 | # └── Protos 159 | # └── HelloWorld 160 | # ├── FooDefinitions 161 | # │ ├── Foo 162 | # │ │ ├── foo-messages.proto 163 | # │ │ └── foo-service.proto 164 | # │ └── grpc-swift-proto-generator-config.json 165 | # ├── HelloWorld.proto 166 | # └── grpc-swift-proto-generator-config.json 167 | 168 | local -r test_dir=$(test_dir_name) 169 | mkdir -p "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo" 170 | cp "${package_manifest}" "${test_dir}/" 171 | cp "${sources}/FooHelloWorldAdopter.swift" "${test_dir}/Sources/" 172 | cp "${protos}/HelloWorld/HelloWorld.proto" "${test_dir}/Sources/Protos/HelloWorld/" 173 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/HelloWorld/grpc-swift-proto-generator-config.json" 174 | cp "${config}/public-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/grpc-swift-proto-generator-config.json" 175 | cp "${protos}/Foo/foo-messages.proto" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo/" 176 | cp "${protos}/Foo/foo-service.proto" "${test_dir}/Sources/Protos/HelloWorld/FooDefinitions/Foo/" 177 | } 178 | 179 | function test_07_duplicated_proto_file_name { 180 | # . 181 | # ├── Package.swift 182 | # └── Sources 183 | # ├── NoOp.swift 184 | # └── Protos 185 | # ├── grpc-swift-proto-generator-config.json 186 | # ├── noop 187 | # │ └── noop.proto 188 | # └── noop2 189 | # └── noop.proto 190 | 191 | local -r test_dir=$(test_dir_name) 192 | mkdir -p "${test_dir}/Sources/Protos" 193 | 194 | cp "${package_manifest}" "${test_dir}/" 195 | mkdir -p "${test_dir}/Sources/Protos" 196 | cp -rp "${protos}/noop" "${test_dir}/Sources/Protos" 197 | cp -rp "${protos}/noop2" "${test_dir}/Sources/Protos" 198 | cp "${sources}/NoOp.swift" "${test_dir}/Sources" 199 | cp "${config}/internal-grpc-swift-proto-generator-config.json" "${test_dir}/Sources/Protos/grpc-swift-proto-generator-config.json" 200 | } 201 | 202 | test_01_top_level_config_file 203 | test_02_peer_config_file 204 | test_03_separate_service_message_protos 205 | test_04_cross_directory_imports 206 | test_05_two_definitions 207 | test_06_nested_definitions 208 | test_07_duplicated_proto_file_name -------------------------------------------------------------------------------- /dev/soundness.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2020, gRPC Authors All rights reserved. 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 | set -eu 17 | 18 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 19 | 20 | function run_logged() { 21 | local message=$1 22 | local command=$2 23 | 24 | log=$(mktemp) 25 | 26 | printf '==> %s ... ' "$message" 27 | 28 | if $command > "$log" 2>&1; then 29 | printf "\033[0;32mOK\033[0m\n" 30 | else 31 | errors=$(( errors + 1)) 32 | printf "\033[0;31mFAILED\033[0m\n" 33 | echo "=== Captured output:" 34 | cat "$log" 35 | echo "===" 36 | fi 37 | } 38 | 39 | function check_license_headers() { 40 | run_logged "Checking license headers" "$here/license-check.sh" 41 | } 42 | 43 | function check_formatting() { 44 | run_logged "Checking formatting" "$here/format.sh -l" 45 | } 46 | 47 | errors=0 48 | check_license_headers 49 | check_formatting 50 | exit $errors 51 | -------------------------------------------------------------------------------- /dev/version-bump.commit.template: -------------------------------------------------------------------------------- 1 | Bump version number to VERSION 2 | 3 | Motivation: 4 | 5 | We plan on tagging a release soon. 6 | 7 | Modifications: 8 | 9 | - Bump the version to VERSION 10 | 11 | Result: 12 | 13 | The version in the default user-agent string will match the released 14 | version. 15 | --------------------------------------------------------------------------------