├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── NOTES.md ├── README.md ├── build.zig ├── build.zig.zon ├── examples ├── all_types.proto ├── conformance.proto ├── gen │ ├── only_enum.pb.zig │ └── only_message.pb.zig ├── google │ └── protobuf │ │ ├── any.proto │ │ ├── compiler │ │ └── plugin.proto │ │ ├── descriptor.proto │ │ ├── duration.proto │ │ ├── field_mask.proto │ │ ├── struct.proto │ │ ├── test_messages_proto2.proto │ │ ├── test_messages_proto3.proto │ │ ├── timestamp.proto │ │ └── wrappers.proto ├── group.proto ├── map.proto ├── message_with_defaults.proto ├── nested.proto ├── oneof-2.proto ├── oneof.proto ├── only_enum.proto ├── person.proto ├── proto3_optional.proto └── recursive.proto ├── gen └── .gitignore ├── script ├── conformance.sh ├── gen-all.sh ├── gen-protobuf-c.sh ├── gen-test.sh ├── gen.sh ├── protoc-capture.sh ├── protoc-decode-text.sh ├── protoc-enc-dec.sh ├── protoc-enc-zig-dec.sh ├── protoc-gen-protoc-c.sh ├── test-all.sh ├── test-single.sh └── zig-decode-text.sh ├── sdk.zig └── src ├── common.zig ├── conformance.zig ├── extern-types.zig ├── gen-c.zig ├── gen-zig.zig ├── gen.zig ├── google └── protobuf │ ├── compiler │ └── plugin.pb.zig │ └── descriptor.pb.zig ├── json.zig ├── lib.zig ├── main.zig ├── meta.zig ├── protobuf-types.zig ├── protobuf.zig ├── protoc-echo-to-stderr.zig ├── test-common.zig ├── test-conformance.zig ├── test-deserialize.zig ├── test-serialize.zig ├── tests.zig ├── types.zig └── unescape-octal.zig /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '0 0 * * 1' 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | optimize: [Debug, ReleaseSafe, ReleaseFast, ReleaseSmall] 13 | runs-on: [ubuntu-latest, macos-latest, windows-latest] 14 | runs-on: ${{ matrix.runs-on }} 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: mlugg/setup-zig@v1 18 | with: 19 | version: 0.14.1 20 | - run: zig build -Doptimize=${{ matrix.optimize }} 21 | - run: zig build test -Doptimize=${{ matrix.optimize }} 22 | lint: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: mlugg/setup-zig@v1 27 | with: 28 | version: 0.14.1 29 | - run: zig fmt --check . 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | .zig-cache/ 3 | zig-out/ 4 | junk/ 5 | src/tmp*.zig -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Travis Staloch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | - [ ] parse text format - this could make for nice readable tests cases 3 | - [ ] output text format 4 | - [ ] maybe parse json? 5 | - [ ] output json 6 | - [ ] conformance testing? 7 | - [ ] support more protoc args? 8 | - [ ] --encode=MESSAGE_TYPE 9 | - [ ] --decode=MESSAGE_TYPE 10 | - [ ] --decode_raw 11 | - [ ] --descriptor_set_in=FILES 12 | - [ ] -oFILE / --descriptor_set_out=FILE 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is in its early early stages. expect bugs and missing features. :warning: 2 | 3 | # About 4 | A tool for generating zig code capable of de/serializing to the protocol buffer wire format. Depends on [protoc](https://developers.google.com/protocol-buffers/docs/downloads) to parse .proto files. 5 | 6 | # Status 7 | - [x] zig code generation 8 | - [ ] recursive message types don't work yet [#1](../../issues/1). see [examples/recursive.proto](examples/recursive.proto) 9 | - [x] deserialization from wire format 10 | - [ ] merging messages not yet implemented - 6 conformance failures 11 | - [x] serialization to wire format 12 | - [x] initial serialization to json format - 9 conformance failures 13 | - [w] conformance testing results: 1489/408/15 success/skip/fail. the 408 skipped are in these categories: 14 | - [ ] json input 15 | - [ ] text format output 16 | - [ ] jspb format output 17 | 18 | # Usage 19 | 20 | * download the [`protoc` compiler](https://protobuf.dev/downloads/) 21 | * make sure `protoc` is in your PATH 22 | 23 | Note: this project has been tested against 24 | ```console 25 | $ protoc --version 26 | libprotoc 21.5 27 | ``` 28 | 29 | 30 | ### Build 31 | ```console 32 | zig build 33 | ``` 34 | 35 | ### Run tests. 36 | note: some of these depend on `protoc` being available. 37 | ```console 38 | zig build test 39 | ``` 40 | 41 | ### Generate .zig files from .proto files 42 | ```console 43 | $ zig build run -- -I examples/ examples/person.proto examples/only_enum.proto 44 | # writes generated content to ./gen/ by default. 45 | # use --zig_out=/gen-path to specify different directory. 46 | ``` 47 | 48 | Note: all arguments after -- are forwarded to `protoc`. 49 | 50 | This is equivalent to: 51 | ```console 52 | $ zig build 53 | $ protoc --plugin=zig-out/bin/protoc-gen-zig --zig_out=gen -I examples/ examples/person.proto examples/only_enum.proto 54 | ``` 55 | 56 | Either of the above generate the following files in gen/: 57 | ```console 58 | $ ls gen 59 | only_enum.pb.zig person.pb.zig 60 | ``` 61 | 62 | ### Use the generated code 63 | * see below for an example `zig test` command 64 | * see [build.zig](build.zig) for a packaging example 65 | ```zig 66 | test "readme" { 67 | // Note - the package 'protobuf' below is src/lib.zig. this package must 68 | // include itself. it can be provided in build.zig or on the command line 69 | // as shown below. 70 | const std = @import("std"); 71 | const pb = @import("protobuf"); 72 | const Person = @import("generated").person.Person; 73 | 74 | // serialize to a writer 75 | const alloc = std.testing.allocator; // could be any zig std.mem.Allocator 76 | var zero = Person.initFields(.{ 77 | .id = 1, 78 | .name = pb.extern_types.String.init("zero"), 79 | .kind = .NONE, 80 | }); 81 | zero.set(.id, 0); 82 | var buf = std.ArrayList(u8).init(alloc); 83 | defer buf.deinit(); 84 | try pb.protobuf.serialize(&zero.base, buf.writer()); 85 | 86 | // deserialize from a buffer 87 | var ctx = pb.protobuf.context(buf.items, alloc); 88 | const message = try ctx.deserialize(&Person.descriptor); 89 | defer message.deinit(alloc); 90 | var zero_copy = try message.as(Person); 91 | 92 | // test that they're equal 93 | try std.testing.expect(zero_copy.has(.id)); 94 | try std.testing.expectEqual(zero.id, zero_copy.id); 95 | try std.testing.expect(zero_copy.has(.name)); 96 | try std.testing.expectEqualStrings(zero.name.slice(), zero_copy.name.slice()); 97 | try std.testing.expect(zero_copy.has(.kind)); 98 | try std.testing.expectEqual(zero.kind, zero_copy.kind); 99 | 100 | // serialize to json 101 | // const stderr = std.io.getStdErr().writer(); 102 | const stderr = std.io.null_writer; 103 | try pb.json.serialize(&zero.base, stderr, .{ 104 | .pretty_print = .{ .indent_size = 2 }, 105 | }); 106 | _ = try stderr.write("\n"); 107 | // prints 108 | //{ 109 | // "name": "zero", 110 | // "id": 0, 111 | // "kind": "NONE" 112 | //} 113 | } 114 | ``` 115 | 116 | ```console 117 | $ zig test src/tests.zig --mod protobuf:protobuf:src/lib.zig --mod generated:protobuf:zig-cache/protobuf-zig/lib.zig --deps protobuf,generated 118 | ``` 119 | 120 | # Resources 121 | ### inspired by 122 | * https://github.com/protobuf-c/protobuf-c 123 | * https://github.com/mlugg/zigpb -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const GenFormat = @import("src/common.zig").GenFormat; 3 | const sdk = @import("sdk.zig"); 4 | 5 | pub fn build(b: *std.Build) !void { 6 | const target = b.standardTargetOptions(.{}); 7 | const optimize = b.standardOptimizeOption(.{}); 8 | 9 | const log_level = b.option( 10 | std.log.Level, 11 | "log-level", 12 | "The log level for the application. default .err", 13 | ) orelse .err; 14 | const echo_hex = b.option( 15 | bool, 16 | "echo-hex", 17 | "protoc-gen-zig will echo contents of stdin as hex instead of raw bytes. useful for capturing results of system protoc commands in hex format.", 18 | ) orelse false; 19 | const gen_format = b.option( 20 | GenFormat, 21 | "gen-format", 22 | "The output format of generated code.", 23 | ) orelse .zig; 24 | const test_filter = b.option( 25 | []const u8, 26 | "test-filter", 27 | "A filter for tests", 28 | ) orelse ""; 29 | 30 | const protobuf_mod = b.addModule("protobuf-zig", .{ 31 | .root_source_file = b.path("src/lib.zig"), 32 | }); 33 | try protobuf_mod.import_table.put(b.allocator, "protobuf", protobuf_mod); 34 | 35 | const build_options = b.addOptions(); 36 | build_options.addOption(std.log.Level, "log_level", log_level); 37 | build_options.addOption(bool, "echo_hex", echo_hex); 38 | build_options.addOption(GenFormat, "output_format", gen_format); 39 | 40 | // for capturing output of system installed protoc. just echoes out whatever protoc sends 41 | const protoc_echo = b.addExecutable(.{ 42 | .name = "protoc-echo-to-stderr", 43 | .root_source_file = b.path("src/protoc-echo-to-stderr.zig"), 44 | .target = target, 45 | .optimize = optimize, 46 | }); 47 | b.installArtifact(protoc_echo); 48 | protoc_echo.root_module.addOptions("build_options", build_options); 49 | 50 | const protoc_gen_zig = b.addExecutable(.{ 51 | .name = "protoc-gen-zig", 52 | .root_source_file = b.path("src/main.zig"), 53 | .target = target, 54 | .optimize = optimize, 55 | }); 56 | b.installArtifact(protoc_gen_zig); 57 | protoc_gen_zig.root_module.addOptions("build_options", build_options); 58 | protoc_gen_zig.root_module.addImport("protobuf", protobuf_mod); 59 | 60 | const dep = b.fmt("protobuf_{s}", .{@tagName(target.result.os.tag)}); 61 | const protoc_filename = if (target.result.os.tag == .windows) "protoc.exe" else "protoc"; 62 | const mprotobuf = b.lazyDependency(dep, .{}); 63 | if (mprotobuf) |protobuf| { 64 | const protoc_path = protobuf.path(b.pathJoin(&.{ "bin", protoc_filename })); 65 | const protoc_bin = b.addInstallBinFile(protoc_path, protoc_filename); 66 | b.getInstallStep().dependOn(&protoc_bin.step); 67 | } 68 | 69 | const run_protoc_cmd = b.addSystemCommand(&.{ 70 | b.getInstallPath(.bin, protoc_filename), 71 | sdk.plugin_arg, 72 | "--zig_out=gen", 73 | }); 74 | 75 | run_protoc_cmd.step.dependOn(b.getInstallStep()); 76 | if (b.args) |args| run_protoc_cmd.addArgs(args); 77 | 78 | const run_step = b.step("run", "Run system protoc using protoc-gen-zig as a plugin. Writes output to gen/ directory by default."); 79 | run_step.dependOn(&run_protoc_cmd.step); 80 | 81 | // generate files that need to be avaliable in tests 82 | var gen_step = try sdk.GenStep.create(b, protoc_filename, protoc_gen_zig, &.{ 83 | "examples/all_types.proto", 84 | "examples/only_enum.proto", 85 | "examples/person.proto", 86 | "examples/oneof-2.proto", 87 | "examples/conformance.proto", 88 | "examples/google/protobuf/wrappers.proto", 89 | "examples/google/protobuf/timestamp.proto", 90 | "examples/google/protobuf/field_mask.proto", 91 | "examples/google/protobuf/duration.proto", 92 | "examples/google/protobuf/any.proto", 93 | "examples/google/protobuf/test_messages_proto3.proto", 94 | "examples/google/protobuf/test_messages_proto2.proto", 95 | "examples/group.proto", 96 | }); 97 | 98 | const main_tests = b.addTest(.{ 99 | .root_source_file = b.path("src/tests.zig"), 100 | .target = target, 101 | .optimize = optimize, 102 | }); 103 | main_tests.root_module.addImport("protobuf", protobuf_mod); 104 | main_tests.root_module.addAnonymousImport("generated", .{ 105 | .root_source_file = gen_step.module.root_source_file, 106 | .imports = &.{.{ .name = "protobuf", .module = protobuf_mod }}, 107 | }); 108 | main_tests.step.dependOn(b.getInstallStep()); 109 | main_tests.step.dependOn(&gen_step.step); 110 | main_tests.filters = if (test_filter.len > 0) 111 | try b.allocator.dupe([]const u8, &.{test_filter}) 112 | else 113 | &.{}; 114 | 115 | const test_step = b.step("test", "Run library tests"); 116 | test_step.dependOn(&b.addRunArtifact(main_tests).step); 117 | 118 | const conformance_exe = b.addExecutable(.{ 119 | .name = "conformance", 120 | .root_source_file = b.path("src/conformance.zig"), 121 | .target = target, 122 | .optimize = optimize, 123 | }); 124 | conformance_exe.root_module.addOptions("build_options", build_options); 125 | conformance_exe.root_module.addImport("protobuf", protobuf_mod); 126 | conformance_exe.root_module.addAnonymousImport("generated", .{ 127 | .root_source_file = gen_step.module.root_source_file, 128 | .imports = &.{.{ .name = "protobuf", .module = protobuf_mod }}, 129 | }); 130 | conformance_exe.step.dependOn(&gen_step.step); 131 | b.installArtifact(conformance_exe); 132 | } 133 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .protobuf_zig, 3 | .version = "0.0.0", 4 | .minimum_zig_version = "0.14.0", 5 | .fingerprint = 0x81ec46ae330b380d, 6 | .dependencies = .{ 7 | .protobuf_linux = .{ 8 | .url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protoc-27.0-linux-x86_64.zip", 9 | .hash = "1220c4aa55042dac45ede1ac2dba768b90e82834f37ebe3e1220b8fa9898f332879d", 10 | .lazy = true, 11 | }, 12 | .protobuf_windows = .{ 13 | .url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protoc-27.0-win64.zip", 14 | .hash = "1220c1b8a1524722abde7316c9ab37760d2823cbce8b52c1305769216cf59e4a92bc", 15 | .lazy = true, 16 | }, 17 | .protobuf_macos = .{ 18 | .url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protoc-27.0-osx-universal_binary.zip", 19 | .hash = "12209cf887c16d2da7c6a8a7ab71223e66f2148097462cade9b83ee9697d1c3d5504", 20 | .lazy = true, 21 | }, 22 | }, 23 | .paths = .{ 24 | "", 25 | //"build.zig", 26 | //"build.zig.zon", 27 | //"src", 28 | //"LICENSE", 29 | //"README.md", 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /examples/all_types.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | message All { 4 | int32 opt_int32 = 1; 5 | int64 opt_int64 = 2; 6 | uint32 opt_uint32 = 3; 7 | uint64 opt_uint64 = 4; 8 | sint32 opt_sint32 = 5; 9 | sint64 opt_sint64 = 6; 10 | fixed32 opt_fixed32 = 7; 11 | fixed64 opt_fixed64 = 8; 12 | sfixed32 opt_sfixed32 = 9; 13 | sfixed64 opt_sfixed64 = 10; 14 | float opt_float = 11; 15 | double opt_double = 12; 16 | bool opt_bool = 13; 17 | string opt_string = 14; 18 | bytes opt_bytes = 15; 19 | 20 | message NestedMessage { 21 | int32 a = 1; 22 | // FIXME 23 | // All corecursive = 2; 24 | } 25 | 26 | enum NestedEnum { 27 | FOO = 0; 28 | BAR = 1; 29 | BAZ = 2; 30 | NEG = -1; // Intentionally negative. 31 | } 32 | 33 | enum AliasedEnum { 34 | option allow_alias = true; 35 | 36 | ALIAS_FOO = 0; 37 | ALIAS_BAR = 1; 38 | ALIAS_BAZ = 2; 39 | MOO = 2; 40 | moo = 2; 41 | bAz = 2; 42 | } 43 | 44 | NestedMessage opt_nested_message = 18; 45 | ForeignMessage opt_foreign_message = 19; 46 | 47 | NestedEnum opt_nested_enum = 21; 48 | ForeignEnum opt_foreign_enum = 22; 49 | AliasedEnum opt_aliased_enum = 23; 50 | 51 | string opt_string_piece = 24 [ctype = STRING_PIECE]; 52 | string opt_cord = 25 [ctype = CORD]; 53 | // FIXME 54 | // All recursive_message = 27; 55 | 56 | repeated int32 rep_int32 = 31; 57 | repeated int64 rep_int64 = 32; 58 | repeated uint32 rep_uint32 = 33; 59 | repeated uint64 rep_uint64 = 34; 60 | repeated sint32 rep_sint32 = 35; 61 | repeated sint64 rep_sint64 = 36; 62 | repeated fixed32 rep_fixed32 = 37; 63 | repeated fixed64 rep_fixed64 = 38; 64 | repeated sfixed32 rep_sfixed32 = 39; 65 | repeated sfixed64 rep_sfixed64 = 40; 66 | repeated float rep_float = 41; 67 | repeated double rep_double = 42; 68 | repeated bool rep_bool = 43; 69 | repeated string rep_string = 44; 70 | repeated bytes rep_bytes = 45; 71 | 72 | repeated NestedMessage rep_nested_message = 48; 73 | repeated ForeignMessage rep_foreign_message = 49; 74 | 75 | repeated NestedEnum rep_nested_enum = 51; 76 | repeated ForeignEnum rep_foreign_enum = 52; 77 | repeated AliasedEnum rep_aliased_enum = 53; 78 | repeated string rep_string_piece = 54 [ctype = STRING_PIECE]; 79 | repeated string rep_cord = 55 [ctype = CORD]; 80 | 81 | // Packed 82 | repeated int32 packed_int32 = 75 [packed = true]; 83 | repeated int64 packed_int64 = 76 [packed = true]; 84 | repeated uint32 packed_uint32 = 77 [packed = true]; 85 | repeated uint64 packed_uint64 = 78 [packed = true]; 86 | repeated sint32 packed_sint32 = 79 [packed = true]; 87 | repeated sint64 packed_sint64 = 80 [packed = true]; 88 | repeated fixed32 packed_fixed32 = 81 [packed = true]; 89 | repeated fixed64 packed_fixed64 = 82 [packed = true]; 90 | repeated sfixed32 packed_sfixed32 = 83 [packed = true]; 91 | repeated sfixed64 packed_sfixed64 = 84 [packed = true]; 92 | repeated float packed_float = 85 [packed = true]; 93 | repeated double packed_double = 86 [packed = true]; 94 | repeated bool packed_bool = 87 [packed = true]; 95 | repeated NestedEnum packed_nested_enum = 88 [packed = true]; 96 | 97 | // Unpacked 98 | repeated int32 unpacked_int32 = 89 [packed = false]; 99 | repeated int64 unpacked_int64 = 90 [packed = false]; 100 | repeated uint32 unpacked_uint32 = 91 [packed = false]; 101 | repeated uint64 unpacked_uint64 = 92 [packed = false]; 102 | repeated sint32 unpacked_sint32 = 93 [packed = false]; 103 | repeated sint64 unpacked_sint64 = 94 [packed = false]; 104 | repeated fixed32 unpacked_fixed32 = 95 [packed = false]; 105 | repeated fixed64 unpacked_fixed64 = 96 [packed = false]; 106 | repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; 107 | repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; 108 | repeated float unpacked_float = 99 [packed = false]; 109 | repeated double unpacked_double = 100 [packed = false]; 110 | repeated bool unpacked_bool = 101 [packed = false]; 111 | repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; 112 | 113 | repeated OneOfMessage oneof_fields = 601; 114 | 115 | message OneOfMessage { 116 | oneof oneof_field { 117 | uint32 oneof_uint32 = 111; 118 | NestedMessage oneof_nested_message = 112; 119 | string oneof_string = 113; 120 | bytes oneof_bytes = 114; 121 | bool oneof_bool = 115; 122 | uint64 oneof_uint64 = 116; 123 | float oneof_float = 117; 124 | double oneof_double = 118; 125 | NestedEnum oneof_enum = 119; 126 | // FIXME 127 | // google.protobuf.NullValue oneof_null_value = 120; 128 | } 129 | } 130 | } 131 | 132 | message ForeignMessage { 133 | int32 c = 1; 134 | } 135 | 136 | enum ForeignEnum { 137 | FOREIGN_FOO = 0; 138 | FOREIGN_BAR = 1; 139 | FOREIGN_BAZ = 2; 140 | } 141 | // FIXME 142 | // import "google/protobuf/struct.proto"; 143 | 144 | -------------------------------------------------------------------------------- /examples/conformance.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | package conformance; 33 | option java_package = "com.google.protobuf.conformance"; 34 | 35 | // This defines the conformance testing protocol. This protocol exists between 36 | // the conformance test suite itself and the code being tested. For each test, 37 | // the suite will send a ConformanceRequest message and expect a 38 | // ConformanceResponse message. 39 | // 40 | // You can either run the tests in two different ways: 41 | // 42 | // 1. in-process (using the interface in conformance_test.h). 43 | // 44 | // 2. as a sub-process communicating over a pipe. Information about how to 45 | // do this is in conformance_test_runner.cc. 46 | // 47 | // Pros/cons of the two approaches: 48 | // 49 | // - running as a sub-process is much simpler for languages other than C/C++. 50 | // 51 | // - running as a sub-process may be more tricky in unusual environments like 52 | // iOS apps, where fork/stdin/stdout are not available. 53 | 54 | enum WireFormat { 55 | UNSPECIFIED = 0; 56 | PROTOBUF = 1; 57 | JSON = 2; 58 | JSPB = 3; // Google internal only. Opensource testees just skip it. 59 | TEXT_FORMAT = 4; 60 | } 61 | 62 | enum TestCategory { 63 | UNSPECIFIED_TEST = 0; 64 | BINARY_TEST = 1; // Test binary wire format. 65 | JSON_TEST = 2; // Test json wire format. 66 | // Similar to JSON_TEST. However, during parsing json, testee should ignore 67 | // unknown fields. This feature is optional. Each implementation can decide 68 | // whether to support it. See 69 | // https://developers.google.com/protocol-buffers/docs/proto3#json_options 70 | // for more detail. 71 | JSON_IGNORE_UNKNOWN_PARSING_TEST = 3; 72 | // Test jspb wire format. Google internal only. Opensource testees just skip it. 73 | JSPB_TEST = 4; 74 | // Test text format. For cpp, java and python, testees can already deal with 75 | // this type. Testees of other languages can simply skip it. 76 | TEXT_FORMAT_TEST = 5; 77 | } 78 | 79 | // The conformance runner will request a list of failures as the first request. 80 | // This will be known by message_type == "conformance.FailureSet", a conformance 81 | // test should return a serialized FailureSet in protobuf_payload. 82 | message FailureSet { 83 | repeated string failure = 1; 84 | } 85 | 86 | // Represents a single test case's input. The testee should: 87 | // 88 | // 1. parse this proto (which should always succeed) 89 | // 2. parse the protobuf or JSON payload in "payload" (which may fail) 90 | // 3. if the parse succeeded, serialize the message in the requested format. 91 | message ConformanceRequest { 92 | // The payload (whether protobuf of JSON) is always for a 93 | // protobuf_test_messages.proto3.TestAllTypes proto (as defined in 94 | // src/google/protobuf/proto3_test_messages.proto). 95 | oneof payload { 96 | bytes protobuf_payload = 1; 97 | string json_payload = 2; 98 | // Google internal only. Opensource testees just skip it. 99 | string jspb_payload = 7; 100 | string text_payload = 8; 101 | } 102 | 103 | // Which format should the testee serialize its message to? 104 | WireFormat requested_output_format = 3; 105 | 106 | // The full name for the test message to use; for the moment, either: 107 | // protobuf_test_messages.proto3.TestAllTypesProto3 or 108 | // protobuf_test_messages.google.protobuf.TestAllTypesProto2. 109 | string message_type = 4; 110 | 111 | // Each test is given a specific test category. Some category may need 112 | // specific support in testee programs. Refer to the definition of TestCategory 113 | // for more information. 114 | TestCategory test_category = 5; 115 | 116 | // Specify details for how to encode jspb. 117 | JspbEncodingConfig jspb_encoding_options = 6; 118 | 119 | // This can be used in json and text format. If true, testee should print 120 | // unknown fields instead of ignore. This feature is optional. 121 | bool print_unknown_fields = 9; 122 | } 123 | 124 | // Represents a single test case's output. 125 | message ConformanceResponse { 126 | oneof result { 127 | // This string should be set to indicate parsing failed. The string can 128 | // provide more information about the parse error if it is available. 129 | // 130 | // Setting this string does not necessarily mean the testee failed the 131 | // test. Some of the test cases are intentionally invalid input. 132 | string parse_error = 1; 133 | 134 | // If the input was successfully parsed but errors occurred when 135 | // serializing it to the requested output format, set the error message in 136 | // this field. 137 | string serialize_error = 6; 138 | 139 | // This should be set if some other error occurred. This will always 140 | // indicate that the test failed. The string can provide more information 141 | // about the failure. 142 | string runtime_error = 2; 143 | 144 | // If the input was successfully parsed and the requested output was 145 | // protobuf, serialize it to protobuf and set it in this field. 146 | bytes protobuf_payload = 3; 147 | 148 | // If the input was successfully parsed and the requested output was JSON, 149 | // serialize to JSON and set it in this field. 150 | string json_payload = 4; 151 | 152 | // For when the testee skipped the test, likely because a certain feature 153 | // wasn't supported, like JSON input/output. 154 | string skipped = 5; 155 | 156 | // If the input was successfully parsed and the requested output was JSPB, 157 | // serialize to JSPB and set it in this field. JSPB is google internal only 158 | // format. Opensource testees can just skip it. 159 | string jspb_payload = 7; 160 | 161 | // If the input was successfully parsed and the requested output was 162 | // TEXT_FORMAT, serialize to TEXT_FORMAT and set it in this field. 163 | string text_payload = 8; 164 | } 165 | } 166 | 167 | // Encoding options for jspb format. 168 | message JspbEncodingConfig { 169 | // Encode the value field of Any as jspb array if true, otherwise binary. 170 | bool use_jspb_array_any_format = 1; 171 | } 172 | 173 | -------------------------------------------------------------------------------- /examples/gen/only_enum.pb.zig: -------------------------------------------------------------------------------- 1 | // --- 2 | // prelude 3 | // --- 4 | 5 | const std = @import("std"); 6 | const pb = @import("protobuf"); 7 | const pbtypes = pb.pbtypes; 8 | const MessageDescriptor = pbtypes.MessageDescriptor; 9 | const Message = pbtypes.Message; 10 | const FieldDescriptor = pbtypes.FieldDescriptor; 11 | const EnumMixins = pbtypes.EnumMixins; 12 | const MessageMixins = pbtypes.MessageMixins; 13 | const FieldFlag = FieldDescriptor.FieldFlag; 14 | const String = pb.extern_types.String; 15 | const ArrayListMut = pb.extern_types.ArrayListMut; 16 | 17 | // --- 18 | // typedefs 19 | // --- 20 | 21 | pub const SomeKind = enum(i32) { 22 | NONE = 0, 23 | A = 1, 24 | B = 2, 25 | C = 3, 26 | 27 | pub usingnamespace EnumMixins(@This()); 28 | }; 29 | // --- 30 | // message types 31 | // --- 32 | 33 | // --- 34 | // tests 35 | // --- 36 | 37 | test { // dummy test for typechecking 38 | std.testing.log_level = .err; // suppress 'required field' warnings 39 | _ = SomeKind; 40 | } 41 | -------------------------------------------------------------------------------- /examples/gen/only_message.pb.zig: -------------------------------------------------------------------------------- 1 | // --- 2 | // prelude 3 | // --- 4 | 5 | const std = @import("std"); 6 | const pb = @import("protobuf"); 7 | const pbtypes = pb.pbtypes; 8 | const MessageDescriptor = pbtypes.MessageDescriptor; 9 | const Message = pbtypes.Message; 10 | const FieldDescriptor = pbtypes.FieldDescriptor; 11 | const EnumMixins = pbtypes.EnumMixins; 12 | const MessageMixins = pbtypes.MessageMixins; 13 | const FieldFlag = FieldDescriptor.FieldFlag; 14 | const String = pb.extern_types.String; 15 | const ArrayListMut = pb.extern_types.ArrayListMut; 16 | const only_enum = @import("only_enum.pb.zig"); 17 | 18 | // --- 19 | // typedefs 20 | // --- 21 | 22 | // --- 23 | // message types 24 | // --- 25 | 26 | pub const Person = extern struct { 27 | base: Message, 28 | name: String = String.empty, 29 | id: i32 = 0, 30 | email: String = String.empty, 31 | kind: only_enum.SomeKind = undefined, 32 | 33 | pub const field_ids = [_]c_uint{ 1, 2, 3, 4 }; 34 | pub const opt_field_ids = [_]c_uint{ 1, 2, 3, 4 }; 35 | 36 | pub usingnamespace MessageMixins(@This()); 37 | pub const field_descriptors = [_]FieldDescriptor{ 38 | FieldDescriptor.init( 39 | "name", 40 | 1, 41 | .LABEL_OPTIONAL, 42 | .TYPE_STRING, 43 | @offsetOf(Person, "name"), 44 | null, 45 | null, 46 | 0, 47 | ), 48 | FieldDescriptor.init( 49 | "id", 50 | 2, 51 | .LABEL_OPTIONAL, 52 | .TYPE_INT32, 53 | @offsetOf(Person, "id"), 54 | null, 55 | null, 56 | 0, 57 | ), 58 | FieldDescriptor.init( 59 | "email", 60 | 3, 61 | .LABEL_OPTIONAL, 62 | .TYPE_STRING, 63 | @offsetOf(Person, "email"), 64 | null, 65 | null, 66 | 0, 67 | ), 68 | FieldDescriptor.init( 69 | "kind", 70 | 4, 71 | .LABEL_OPTIONAL, 72 | .TYPE_ENUM, 73 | @offsetOf(Person, "kind"), 74 | &only_enum.SomeKind.descriptor, 75 | null, 76 | 0, 77 | ), 78 | }; 79 | }; 80 | 81 | // --- 82 | // tests 83 | // --- 84 | 85 | test { // dummy test for typechecking 86 | std.testing.log_level = .err; // suppress 'required field' warnings 87 | _ = Person; 88 | _ = Person.descriptor; 89 | } 90 | -------------------------------------------------------------------------------- /examples/google/protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/anypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := anypb.New(foo) 81 | // if err != nil { 82 | // ... 83 | // } 84 | // ... 85 | // foo := &pb.Foo{} 86 | // if err := any.UnmarshalTo(foo); err != nil { 87 | // ... 88 | // } 89 | // 90 | // The pack methods provided by protobuf library will by default use 91 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 92 | // methods only use the fully qualified type name after the last '/' 93 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 94 | // name "y.z". 95 | // 96 | // 97 | // JSON 98 | // 99 | // The JSON representation of an `Any` value uses the regular 100 | // representation of the deserialized, embedded message, with an 101 | // additional field `@type` which contains the type URL. Example: 102 | // 103 | // package google.profile; 104 | // message Person { 105 | // string first_name = 1; 106 | // string last_name = 2; 107 | // } 108 | // 109 | // { 110 | // "@type": "type.googleapis.com/google.profile.Person", 111 | // "firstName": , 112 | // "lastName": 113 | // } 114 | // 115 | // If the embedded message type is well-known and has a custom JSON 116 | // representation, that representation will be embedded adding a field 117 | // `value` which holds the custom JSON in addition to the `@type` 118 | // field. Example (for message [google.protobuf.Duration][]): 119 | // 120 | // { 121 | // "@type": "type.googleapis.com/google.protobuf.Duration", 122 | // "value": "1.212s" 123 | // } 124 | // 125 | message Any { 126 | // A URL/resource name that uniquely identifies the type of the serialized 127 | // protocol buffer message. This string must contain at least 128 | // one "/" character. The last segment of the URL's path must represent 129 | // the fully qualified name of the type (as in 130 | // `path/google.protobuf.Duration`). The name should be in a canonical form 131 | // (e.g., leading "." is not accepted). 132 | // 133 | // In practice, teams usually precompile into the binary all types that they 134 | // expect it to use in the context of Any. However, for URLs which use the 135 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 136 | // server that maps type URLs to message definitions as follows: 137 | // 138 | // * If no scheme is provided, `https` is assumed. 139 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 140 | // value in binary format, or produce an error. 141 | // * Applications are allowed to cache lookup results based on the 142 | // URL, or have them precompiled into a binary to avoid any 143 | // lookup. Therefore, binary compatibility needs to be preserved 144 | // on changes to types. (Use versioned type names to manage 145 | // breaking changes.) 146 | // 147 | // Note: this functionality is not currently available in the official 148 | // protobuf release, and it is not used for type URLs beginning with 149 | // type.googleapis.com. 150 | // 151 | // Schemes other than `http`, `https` (or the empty scheme) might be 152 | // used with implementation specific semantics. 153 | // 154 | string type_url = 1; 155 | 156 | // Must be a valid serialized protocol buffer of the above specified type. 157 | bytes value = 2; 158 | } 159 | -------------------------------------------------------------------------------- /examples/google/protobuf/compiler/plugin.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Author: kenton@google.com (Kenton Varda) 32 | // 33 | // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is 34 | // just a program that reads a CodeGeneratorRequest from stdin and writes a 35 | // CodeGeneratorResponse to stdout. 36 | // 37 | // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead 38 | // of dealing with the raw protocol defined here. 39 | // 40 | // A plugin executable needs only to be placed somewhere in the path. The 41 | // plugin should be named "protoc-gen-$NAME", and will then be used when the 42 | // flag "--${NAME}_out" is passed to protoc. 43 | 44 | syntax = "proto2"; 45 | 46 | package google.protobuf.compiler; 47 | option java_package = "com.google.protobuf.compiler"; 48 | option java_outer_classname = "PluginProtos"; 49 | 50 | option go_package = "google.golang.org/protobuf/types/pluginpb"; 51 | 52 | import "google/protobuf/descriptor.proto"; 53 | 54 | // The version number of protocol compiler. 55 | message Version { 56 | optional int32 major = 1; 57 | optional int32 minor = 2; 58 | optional int32 patch = 3; 59 | // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should 60 | // be empty for mainline stable releases. 61 | optional string suffix = 4; 62 | } 63 | 64 | // An encoded CodeGeneratorRequest is written to the plugin's stdin. 65 | message CodeGeneratorRequest { 66 | // The .proto files that were explicitly listed on the command-line. The 67 | // code generator should generate code only for these files. Each file's 68 | // descriptor will be included in proto_file, below. 69 | repeated string file_to_generate = 1; 70 | 71 | // The generator parameter passed on the command-line. 72 | optional string parameter = 2; 73 | 74 | // FileDescriptorProtos for all files in files_to_generate and everything 75 | // they import. The files will appear in topological order, so each file 76 | // appears before any file that imports it. 77 | // 78 | // protoc guarantees that all proto_files will be written after 79 | // the fields above, even though this is not technically guaranteed by the 80 | // protobuf wire format. This theoretically could allow a plugin to stream 81 | // in the FileDescriptorProtos and handle them one by one rather than read 82 | // the entire set into memory at once. However, as of this writing, this 83 | // is not similarly optimized on protoc's end -- it will store all fields in 84 | // memory at once before sending them to the plugin. 85 | // 86 | // Type names of fields and extensions in the FileDescriptorProto are always 87 | // fully qualified. 88 | repeated FileDescriptorProto proto_file = 15; 89 | 90 | // The version number of protocol compiler. 91 | optional Version compiler_version = 3; 92 | 93 | } 94 | 95 | // The plugin writes an encoded CodeGeneratorResponse to stdout. 96 | message CodeGeneratorResponse { 97 | // Error message. If non-empty, code generation failed. The plugin process 98 | // should exit with status code zero even if it reports an error in this way. 99 | // 100 | // This should be used to indicate errors in .proto files which prevent the 101 | // code generator from generating correct code. Errors which indicate a 102 | // problem in protoc itself -- such as the input CodeGeneratorRequest being 103 | // unparseable -- should be reported by writing a message to stderr and 104 | // exiting with a non-zero status code. 105 | optional string error = 1; 106 | 107 | // A bitmask of supported features that the code generator supports. 108 | // This is a bitwise "or" of values from the Feature enum. 109 | optional uint64 supported_features = 2; 110 | 111 | // Sync with code_generator.h. 112 | enum Feature { 113 | FEATURE_NONE = 0; 114 | FEATURE_PROTO3_OPTIONAL = 1; 115 | } 116 | 117 | // Represents a single generated file. 118 | message File { 119 | // The file name, relative to the output directory. The name must not 120 | // contain "." or ".." components and must be relative, not be absolute (so, 121 | // the file cannot lie outside the output directory). "/" must be used as 122 | // the path separator, not "\". 123 | // 124 | // If the name is omitted, the content will be appended to the previous 125 | // file. This allows the generator to break large files into small chunks, 126 | // and allows the generated text to be streamed back to protoc so that large 127 | // files need not reside completely in memory at one time. Note that as of 128 | // this writing protoc does not optimize for this -- it will read the entire 129 | // CodeGeneratorResponse before writing files to disk. 130 | optional string name = 1; 131 | 132 | // If non-empty, indicates that the named file should already exist, and the 133 | // content here is to be inserted into that file at a defined insertion 134 | // point. This feature allows a code generator to extend the output 135 | // produced by another code generator. The original generator may provide 136 | // insertion points by placing special annotations in the file that look 137 | // like: 138 | // @@protoc_insertion_point(NAME) 139 | // The annotation can have arbitrary text before and after it on the line, 140 | // which allows it to be placed in a comment. NAME should be replaced with 141 | // an identifier naming the point -- this is what other generators will use 142 | // as the insertion_point. Code inserted at this point will be placed 143 | // immediately above the line containing the insertion point (thus multiple 144 | // insertions to the same point will come out in the order they were added). 145 | // The double-@ is intended to make it unlikely that the generated code 146 | // could contain things that look like insertion points by accident. 147 | // 148 | // For example, the C++ code generator places the following line in the 149 | // .pb.h files that it generates: 150 | // // @@protoc_insertion_point(namespace_scope) 151 | // This line appears within the scope of the file's package namespace, but 152 | // outside of any particular class. Another plugin can then specify the 153 | // insertion_point "namespace_scope" to generate additional classes or 154 | // other declarations that should be placed in this scope. 155 | // 156 | // Note that if the line containing the insertion point begins with 157 | // whitespace, the same whitespace will be added to every line of the 158 | // inserted text. This is useful for languages like Python, where 159 | // indentation matters. In these languages, the insertion point comment 160 | // should be indented the same amount as any inserted code will need to be 161 | // in order to work correctly in that context. 162 | // 163 | // The code generator that generates the initial file and the one which 164 | // inserts into it must both run as part of a single invocation of protoc. 165 | // Code generators are executed in the order in which they appear on the 166 | // command line. 167 | // 168 | // If |insertion_point| is present, |name| must also be present. 169 | optional string insertion_point = 2; 170 | 171 | // The file contents. 172 | optional string content = 15; 173 | 174 | // Information describing the file content being inserted. If an insertion 175 | // point is used, this information will be appropriately offset and inserted 176 | // into the code generation metadata for the generated files. 177 | optional GeneratedCodeInfo generated_code_info = 16; 178 | } 179 | repeated File file = 15; 180 | } 181 | -------------------------------------------------------------------------------- /examples/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/durationpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (duration.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | // Signed seconds of the span of time. Must be from -315,576,000,000 105 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 106 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 107 | int64 seconds = 1; 108 | 109 | // Signed fractions of a second at nanosecond resolution of the span 110 | // of time. Durations less than one second are represented with a 0 111 | // `seconds` field and a positive or negative `nanos` field. For durations 112 | // of one second or more, a non-zero value for the `nanos` field must be 113 | // of the same sign as the `seconds` field. Must be from -999,999,999 114 | // to +999,999,999 inclusive. 115 | int32 nanos = 2; 116 | } 117 | -------------------------------------------------------------------------------- /examples/google/protobuf/field_mask.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "FieldMaskProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; 41 | option cc_enable_arenas = true; 42 | 43 | // `FieldMask` represents a set of symbolic field paths, for example: 44 | // 45 | // paths: "f.a" 46 | // paths: "f.b.d" 47 | // 48 | // Here `f` represents a field in some root message, `a` and `b` 49 | // fields in the message found in `f`, and `d` a field found in the 50 | // message in `f.b`. 51 | // 52 | // Field masks are used to specify a subset of fields that should be 53 | // returned by a get operation or modified by an update operation. 54 | // Field masks also have a custom JSON encoding (see below). 55 | // 56 | // # Field Masks in Projections 57 | // 58 | // When used in the context of a projection, a response message or 59 | // sub-message is filtered by the API to only contain those fields as 60 | // specified in the mask. For example, if the mask in the previous 61 | // example is applied to a response message as follows: 62 | // 63 | // f { 64 | // a : 22 65 | // b { 66 | // d : 1 67 | // x : 2 68 | // } 69 | // y : 13 70 | // } 71 | // z: 8 72 | // 73 | // The result will not contain specific values for fields x,y and z 74 | // (their value will be set to the default, and omitted in proto text 75 | // output): 76 | // 77 | // 78 | // f { 79 | // a : 22 80 | // b { 81 | // d : 1 82 | // } 83 | // } 84 | // 85 | // A repeated field is not allowed except at the last position of a 86 | // paths string. 87 | // 88 | // If a FieldMask object is not present in a get operation, the 89 | // operation applies to all fields (as if a FieldMask of all fields 90 | // had been specified). 91 | // 92 | // Note that a field mask does not necessarily apply to the 93 | // top-level response message. In case of a REST get operation, the 94 | // field mask applies directly to the response, but in case of a REST 95 | // list operation, the mask instead applies to each individual message 96 | // in the returned resource list. In case of a REST custom method, 97 | // other definitions may be used. Where the mask applies will be 98 | // clearly documented together with its declaration in the API. In 99 | // any case, the effect on the returned resource/resources is required 100 | // behavior for APIs. 101 | // 102 | // # Field Masks in Update Operations 103 | // 104 | // A field mask in update operations specifies which fields of the 105 | // targeted resource are going to be updated. The API is required 106 | // to only change the values of the fields as specified in the mask 107 | // and leave the others untouched. If a resource is passed in to 108 | // describe the updated values, the API ignores the values of all 109 | // fields not covered by the mask. 110 | // 111 | // If a repeated field is specified for an update operation, new values will 112 | // be appended to the existing repeated field in the target resource. Note that 113 | // a repeated field is only allowed in the last position of a `paths` string. 114 | // 115 | // If a sub-message is specified in the last position of the field mask for an 116 | // update operation, then new value will be merged into the existing sub-message 117 | // in the target resource. 118 | // 119 | // For example, given the target message: 120 | // 121 | // f { 122 | // b { 123 | // d: 1 124 | // x: 2 125 | // } 126 | // c: [1] 127 | // } 128 | // 129 | // And an update message: 130 | // 131 | // f { 132 | // b { 133 | // d: 10 134 | // } 135 | // c: [2] 136 | // } 137 | // 138 | // then if the field mask is: 139 | // 140 | // paths: ["f.b", "f.c"] 141 | // 142 | // then the result will be: 143 | // 144 | // f { 145 | // b { 146 | // d: 10 147 | // x: 2 148 | // } 149 | // c: [1, 2] 150 | // } 151 | // 152 | // An implementation may provide options to override this default behavior for 153 | // repeated and message fields. 154 | // 155 | // In order to reset a field's value to the default, the field must 156 | // be in the mask and set to the default value in the provided resource. 157 | // Hence, in order to reset all fields of a resource, provide a default 158 | // instance of the resource and set all fields in the mask, or do 159 | // not provide a mask as described below. 160 | // 161 | // If a field mask is not present on update, the operation applies to 162 | // all fields (as if a field mask of all fields has been specified). 163 | // Note that in the presence of schema evolution, this may mean that 164 | // fields the client does not know and has therefore not filled into 165 | // the request will be reset to their default. If this is unwanted 166 | // behavior, a specific service may require a client to always specify 167 | // a field mask, producing an error if not. 168 | // 169 | // As with get operations, the location of the resource which 170 | // describes the updated values in the request message depends on the 171 | // operation kind. In any case, the effect of the field mask is 172 | // required to be honored by the API. 173 | // 174 | // ## Considerations for HTTP REST 175 | // 176 | // The HTTP kind of an update operation which uses a field mask must 177 | // be set to PATCH instead of PUT in order to satisfy HTTP semantics 178 | // (PUT must only be used for full updates). 179 | // 180 | // # JSON Encoding of Field Masks 181 | // 182 | // In JSON, a field mask is encoded as a single string where paths are 183 | // separated by a comma. Fields name in each path are converted 184 | // to/from lower-camel naming conventions. 185 | // 186 | // As an example, consider the following message declarations: 187 | // 188 | // message Profile { 189 | // User user = 1; 190 | // Photo photo = 2; 191 | // } 192 | // message User { 193 | // string display_name = 1; 194 | // string address = 2; 195 | // } 196 | // 197 | // In proto a field mask for `Profile` may look as such: 198 | // 199 | // mask { 200 | // paths: "user.display_name" 201 | // paths: "photo" 202 | // } 203 | // 204 | // In JSON, the same mask is represented as below: 205 | // 206 | // { 207 | // mask: "user.displayName,photo" 208 | // } 209 | // 210 | // # Field Masks and Oneof Fields 211 | // 212 | // Field masks treat fields in oneofs just as regular fields. Consider the 213 | // following message: 214 | // 215 | // message SampleMessage { 216 | // oneof test_oneof { 217 | // string name = 4; 218 | // SubMessage sub_message = 9; 219 | // } 220 | // } 221 | // 222 | // The field mask can be: 223 | // 224 | // mask { 225 | // paths: "name" 226 | // } 227 | // 228 | // Or: 229 | // 230 | // mask { 231 | // paths: "sub_message" 232 | // } 233 | // 234 | // Note that oneof type names ("test_oneof" in this case) cannot be used in 235 | // paths. 236 | // 237 | // ## Field Mask Verification 238 | // 239 | // The implementation of any API method which has a FieldMask type field in the 240 | // request should verify the included field paths, and return an 241 | // `INVALID_ARGUMENT` error if any path is unmappable. 242 | message FieldMask { 243 | // The set of field mask paths. 244 | repeated string paths = 1; 245 | } 246 | -------------------------------------------------------------------------------- /examples/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // `Struct` represents a structured data value, consisting of fields 44 | // which map to dynamically typed values. In some languages, `Struct` 45 | // might be supported by a native representation. For example, in 46 | // scripting languages like JS a struct is represented as an 47 | // object. The details of that representation are described together 48 | // with the proto support for the language. 49 | // 50 | // The JSON representation for `Struct` is JSON object. 51 | message Struct { 52 | // Unordered map of dynamically typed values. 53 | map fields = 1; 54 | } 55 | 56 | // `Value` represents a dynamically typed value which can be either 57 | // null, a number, a string, a boolean, a recursive struct value, or a 58 | // list of values. A producer of value is expected to set one of these 59 | // variants. Absence of any variant indicates an error. 60 | // 61 | // The JSON representation for `Value` is JSON value. 62 | message Value { 63 | // The kind of value. 64 | oneof kind { 65 | // Represents a null value. 66 | NullValue null_value = 1; 67 | // Represents a double value. 68 | double number_value = 2; 69 | // Represents a string value. 70 | string string_value = 3; 71 | // Represents a boolean value. 72 | bool bool_value = 4; 73 | // Represents a structured value. 74 | // FIXME 75 | // Struct struct_value = 5; 76 | // Represents a repeated `Value`. 77 | // FIXME 78 | // ListValue list_value = 6; 79 | } 80 | } 81 | 82 | // `NullValue` is a singleton enumeration to represent the null value for the 83 | // `Value` type union. 84 | // 85 | // The JSON representation for `NullValue` is JSON `null`. 86 | enum NullValue { 87 | // Null value. 88 | NULL_VALUE = 0; 89 | } 90 | 91 | // `ListValue` is a wrapper around a repeated field of values. 92 | // 93 | // The JSON representation for `ListValue` is JSON array. 94 | message ListValue { 95 | // Repeated field of dynamically typed values. 96 | repeated Value values = 1; 97 | } 98 | -------------------------------------------------------------------------------- /examples/google/protobuf/test_messages_proto2.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // 31 | // Test schema for proto2 messages. This test schema is used by: 32 | // 33 | // - conformance tests 34 | // 35 | 36 | // LINT: ALLOW_GROUPS 37 | 38 | syntax = "proto2"; 39 | 40 | package protobuf_test_messages.proto2; 41 | 42 | option java_package = "com.google.protobuf_test_messages.proto2"; 43 | 44 | // This is the default, but we specify it here explicitly. 45 | option optimize_for = SPEED; 46 | 47 | option cc_enable_arenas = true; 48 | 49 | // This proto includes every type of field in both singular and repeated 50 | // forms. 51 | // 52 | // Also, crucially, all messages and enums in this file are eventually 53 | // submessages of this message. So for example, a fuzz test of TestAllTypes 54 | // could trigger bugs that occur in any message type in this file. We verify 55 | // this stays true in a unit test. 56 | message TestAllTypesProto2 { 57 | message NestedMessage { 58 | optional int32 a = 1; 59 | // FIXME 60 | // optional TestAllTypesProto2 corecursive = 2; 61 | } 62 | 63 | enum NestedEnum { 64 | FOO = 0; 65 | BAR = 1; 66 | BAZ = 2; 67 | NEG = -1; // Intentionally negative. 68 | } 69 | 70 | // Singular 71 | optional int32 optional_int32 = 1; 72 | optional int64 optional_int64 = 2; 73 | optional uint32 optional_uint32 = 3; 74 | optional uint64 optional_uint64 = 4; 75 | optional sint32 optional_sint32 = 5; 76 | optional sint64 optional_sint64 = 6; 77 | optional fixed32 optional_fixed32 = 7; 78 | optional fixed64 optional_fixed64 = 8; 79 | optional sfixed32 optional_sfixed32 = 9; 80 | optional sfixed64 optional_sfixed64 = 10; 81 | optional float optional_float = 11; 82 | optional double optional_double = 12; 83 | optional bool optional_bool = 13; 84 | optional string optional_string = 14; 85 | optional bytes optional_bytes = 15; 86 | 87 | // FIXME 88 | // optional NestedMessage optional_nested_message = 18; 89 | // FIXME 90 | // optional ForeignMessageProto2 optional_foreign_message = 19; 91 | 92 | optional NestedEnum optional_nested_enum = 21; 93 | optional ForeignEnumProto2 optional_foreign_enum = 22; 94 | 95 | optional string optional_string_piece = 24 [ctype = STRING_PIECE]; 96 | optional string optional_cord = 25 [ctype = CORD]; 97 | 98 | // FIXME 99 | // optional TestAllTypesProto2 recursive_message = 27; 100 | 101 | // Repeated 102 | repeated int32 repeated_int32 = 31; 103 | repeated int64 repeated_int64 = 32; 104 | repeated uint32 repeated_uint32 = 33; 105 | repeated uint64 repeated_uint64 = 34; 106 | repeated sint32 repeated_sint32 = 35; 107 | repeated sint64 repeated_sint64 = 36; 108 | repeated fixed32 repeated_fixed32 = 37; 109 | repeated fixed64 repeated_fixed64 = 38; 110 | repeated sfixed32 repeated_sfixed32 = 39; 111 | repeated sfixed64 repeated_sfixed64 = 40; 112 | repeated float repeated_float = 41; 113 | repeated double repeated_double = 42; 114 | repeated bool repeated_bool = 43; 115 | repeated string repeated_string = 44; 116 | repeated bytes repeated_bytes = 45; 117 | 118 | repeated NestedMessage repeated_nested_message = 48; 119 | repeated ForeignMessageProto2 repeated_foreign_message = 49; 120 | 121 | repeated NestedEnum repeated_nested_enum = 51; 122 | repeated ForeignEnumProto2 repeated_foreign_enum = 52; 123 | 124 | repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; 125 | repeated string repeated_cord = 55 [ctype = CORD]; 126 | 127 | // Packed 128 | repeated int32 packed_int32 = 75 [packed = true]; 129 | repeated int64 packed_int64 = 76 [packed = true]; 130 | repeated uint32 packed_uint32 = 77 [packed = true]; 131 | repeated uint64 packed_uint64 = 78 [packed = true]; 132 | repeated sint32 packed_sint32 = 79 [packed = true]; 133 | repeated sint64 packed_sint64 = 80 [packed = true]; 134 | repeated fixed32 packed_fixed32 = 81 [packed = true]; 135 | repeated fixed64 packed_fixed64 = 82 [packed = true]; 136 | repeated sfixed32 packed_sfixed32 = 83 [packed = true]; 137 | repeated sfixed64 packed_sfixed64 = 84 [packed = true]; 138 | repeated float packed_float = 85 [packed = true]; 139 | repeated double packed_double = 86 [packed = true]; 140 | repeated bool packed_bool = 87 [packed = true]; 141 | repeated NestedEnum packed_nested_enum = 88 [packed = true]; 142 | 143 | // Unpacked 144 | repeated int32 unpacked_int32 = 89 [packed = false]; 145 | repeated int64 unpacked_int64 = 90 [packed = false]; 146 | repeated uint32 unpacked_uint32 = 91 [packed = false]; 147 | repeated uint64 unpacked_uint64 = 92 [packed = false]; 148 | repeated sint32 unpacked_sint32 = 93 [packed = false]; 149 | repeated sint64 unpacked_sint64 = 94 [packed = false]; 150 | repeated fixed32 unpacked_fixed32 = 95 [packed = false]; 151 | repeated fixed64 unpacked_fixed64 = 96 [packed = false]; 152 | repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; 153 | repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; 154 | repeated float unpacked_float = 99 [packed = false]; 155 | repeated double unpacked_double = 100 [packed = false]; 156 | repeated bool unpacked_bool = 101 [packed = false]; 157 | repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; 158 | 159 | // Map 160 | map map_int32_int32 = 56; 161 | map map_int64_int64 = 57; 162 | map map_uint32_uint32 = 58; 163 | map map_uint64_uint64 = 59; 164 | map map_sint32_sint32 = 60; 165 | map map_sint64_sint64 = 61; 166 | map map_fixed32_fixed32 = 62; 167 | map map_fixed64_fixed64 = 63; 168 | map map_sfixed32_sfixed32 = 64; 169 | map map_sfixed64_sfixed64 = 65; 170 | map map_int32_float = 66; 171 | map map_int32_double = 67; 172 | map map_bool_bool = 68; 173 | map map_string_string = 69; 174 | map map_string_bytes = 70; 175 | map map_string_nested_message = 71; 176 | map map_string_foreign_message = 72; 177 | map map_string_nested_enum = 73; 178 | map map_string_foreign_enum = 74; 179 | 180 | oneof oneof_field { 181 | uint32 oneof_uint32 = 111; 182 | NestedMessage oneof_nested_message = 112; 183 | string oneof_string = 113; 184 | bytes oneof_bytes = 114; 185 | bool oneof_bool = 115; 186 | uint64 oneof_uint64 = 116; 187 | float oneof_float = 117; 188 | double oneof_double = 118; 189 | NestedEnum oneof_enum = 119; 190 | } 191 | 192 | // extensions 193 | extensions 120 to 200; 194 | 195 | // groups 196 | optional group Data = 201 { 197 | optional int32 group_int32 = 202; 198 | optional uint32 group_uint32 = 203; 199 | } 200 | 201 | // default values 202 | optional int32 default_int32 = 241 [ default = -123456789]; 203 | optional int64 default_int64 = 242 [ default = -9123456789123456789]; 204 | optional uint32 default_uint32 = 243 [ default = 2123456789]; 205 | optional uint64 default_uint64 = 244 [ default = 10123456789123456789]; 206 | optional sint32 default_sint32 = 245 [ default = -123456789]; 207 | optional sint64 default_sint64 = 246 [default = -9123456789123456789]; 208 | optional fixed32 default_fixed32 = 247 [ default = 2123456789]; 209 | optional fixed64 default_fixed64 = 248 [ default = 10123456789123456789]; 210 | optional sfixed32 default_sfixed32 = 249 [ default = -123456789]; 211 | optional sfixed64 default_sfixed64 = 250 [default = -9123456789123456789]; 212 | optional float default_float = 251 [ default = 9e9]; 213 | optional double default_double = 252 [ default = 7e22]; 214 | optional bool default_bool = 253 [ default = true]; 215 | optional string default_string = 254 [ default = "Rosebud"]; 216 | optional bytes default_bytes = 255 [ default = "joshua"]; 217 | 218 | // Test field-name-to-JSON-name convention. 219 | // (protobuf says names can be any valid C/C++ identifier.) 220 | optional int32 fieldname1 = 401; 221 | optional int32 field_name2 = 402; 222 | optional int32 _field_name3 = 403; 223 | optional int32 field__name4_ = 404; 224 | optional int32 field0name5 = 405; 225 | optional int32 field_0_name6 = 406; 226 | optional int32 fieldName7 = 407; 227 | optional int32 FieldName8 = 408; 228 | optional int32 field_Name9 = 409; 229 | optional int32 Field_Name10 = 410; 230 | optional int32 FIELD_NAME11 = 411; 231 | optional int32 FIELD_name12 = 412; 232 | optional int32 __field_name13 = 413; 233 | optional int32 __Field_name14 = 414; 234 | optional int32 field__name15 = 415; 235 | optional int32 field__Name16 = 416; 236 | optional int32 field_name17__ = 417; 237 | optional int32 Field_name18__ = 418; 238 | 239 | // Reserved for unknown fields test. 240 | reserved 1000 to 9999; 241 | 242 | // message_set test case. 243 | message MessageSetCorrect { 244 | option message_set_wire_format = true; 245 | 246 | extensions 4 to max; 247 | } 248 | 249 | message MessageSetCorrectExtension1 { 250 | extend MessageSetCorrect { 251 | optional MessageSetCorrectExtension1 message_set_extension = 1547769; 252 | } 253 | optional string str = 25; 254 | } 255 | 256 | message MessageSetCorrectExtension2 { 257 | extend MessageSetCorrect { 258 | optional MessageSetCorrectExtension2 message_set_extension = 4135312; 259 | } 260 | optional int32 i = 9; 261 | } 262 | } 263 | 264 | message ForeignMessageProto2 { 265 | optional int32 c = 1; 266 | } 267 | 268 | enum ForeignEnumProto2 { 269 | FOREIGN_FOO = 0; 270 | FOREIGN_BAR = 1; 271 | FOREIGN_BAZ = 2; 272 | } 273 | 274 | extend TestAllTypesProto2 { 275 | optional int32 extension_int32 = 120; 276 | } 277 | 278 | message UnknownToTestAllTypes { 279 | optional int32 optional_int32 = 1001; 280 | optional string optional_string = 1002; 281 | optional ForeignMessageProto2 nested_message = 1003; 282 | optional group OptionalGroup = 1004 { 283 | optional int32 a = 1; 284 | } 285 | optional bool optional_bool = 1006; 286 | repeated int32 repeated_int32 = 1011; 287 | } 288 | 289 | message NullHypothesisProto2 { 290 | } 291 | 292 | message EnumOnlyProto2 { 293 | enum Bool { 294 | kFalse = 0; 295 | kTrue = 1; 296 | } 297 | } 298 | 299 | message OneStringProto2 { 300 | optional string data = 1; 301 | } 302 | 303 | message ProtoWithKeywords { 304 | optional int32 inline = 1; 305 | optional string concept = 2; 306 | repeated string requires = 3; 307 | } 308 | -------------------------------------------------------------------------------- /examples/google/protobuf/test_messages_proto3.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // 31 | // Test schema for proto3 messages. This test schema is used by: 32 | // 33 | // - benchmarks 34 | // - fuzz tests 35 | // - conformance tests 36 | // 37 | 38 | syntax = "proto3"; 39 | 40 | package protobuf_test_messages.proto3; 41 | 42 | option java_package = "com.google.protobuf_test_messages.proto3"; 43 | option objc_class_prefix = "Proto3"; 44 | 45 | // This is the default, but we specify it here explicitly. 46 | option optimize_for = SPEED; 47 | 48 | import "google/protobuf/any.proto"; 49 | import "google/protobuf/duration.proto"; 50 | import "google/protobuf/field_mask.proto"; 51 | // FIXME 52 | // import "google/protobuf/struct.proto"; 53 | import "google/protobuf/timestamp.proto"; 54 | import "google/protobuf/wrappers.proto"; 55 | 56 | option cc_enable_arenas = true; 57 | 58 | // This proto includes every type of field in both singular and repeated 59 | // forms. 60 | // 61 | // Also, crucially, all messages and enums in this file are eventually 62 | // submessages of this message. So for example, a fuzz test of TestAllTypes 63 | // could trigger bugs that occur in any message type in this file. We verify 64 | // this stays true in a unit test. 65 | message TestAllTypesProto3 { 66 | message NestedMessage { 67 | int32 a = 1; 68 | // FIXME 69 | // TestAllTypesProto3 corecursive = 2; 70 | } 71 | 72 | enum NestedEnum { 73 | FOO = 0; 74 | BAR = 1; 75 | BAZ = 2; 76 | NEG = -1; // Intentionally negative. 77 | } 78 | 79 | enum AliasedEnum { 80 | option allow_alias = true; 81 | 82 | ALIAS_FOO = 0; 83 | ALIAS_BAR = 1; 84 | ALIAS_BAZ = 2; 85 | MOO = 2; 86 | moo = 2; 87 | bAz = 2; 88 | } 89 | 90 | // Singular 91 | int32 optional_int32 = 1; 92 | int64 optional_int64 = 2; 93 | uint32 optional_uint32 = 3; 94 | uint64 optional_uint64 = 4; 95 | sint32 optional_sint32 = 5; 96 | sint64 optional_sint64 = 6; 97 | fixed32 optional_fixed32 = 7; 98 | fixed64 optional_fixed64 = 8; 99 | sfixed32 optional_sfixed32 = 9; 100 | sfixed64 optional_sfixed64 = 10; 101 | float optional_float = 11; 102 | double optional_double = 12; 103 | bool optional_bool = 13; 104 | string optional_string = 14; 105 | bytes optional_bytes = 15; 106 | 107 | NestedMessage optional_nested_message = 18; 108 | ForeignMessage optional_foreign_message = 19; 109 | 110 | NestedEnum optional_nested_enum = 21; 111 | ForeignEnum optional_foreign_enum = 22; 112 | AliasedEnum optional_aliased_enum = 23; 113 | 114 | string optional_string_piece = 24 [ctype = STRING_PIECE]; 115 | string optional_cord = 25 [ctype = CORD]; 116 | 117 | // FIXME 118 | // TestAllTypesProto3 recursive_message = 27; 119 | 120 | // Repeated 121 | repeated int32 repeated_int32 = 31; 122 | repeated int64 repeated_int64 = 32; 123 | repeated uint32 repeated_uint32 = 33; 124 | repeated uint64 repeated_uint64 = 34; 125 | repeated sint32 repeated_sint32 = 35; 126 | repeated sint64 repeated_sint64 = 36; 127 | repeated fixed32 repeated_fixed32 = 37; 128 | repeated fixed64 repeated_fixed64 = 38; 129 | repeated sfixed32 repeated_sfixed32 = 39; 130 | repeated sfixed64 repeated_sfixed64 = 40; 131 | repeated float repeated_float = 41; 132 | repeated double repeated_double = 42; 133 | repeated bool repeated_bool = 43; 134 | repeated string repeated_string = 44; 135 | repeated bytes repeated_bytes = 45; 136 | 137 | repeated NestedMessage repeated_nested_message = 48; 138 | repeated ForeignMessage repeated_foreign_message = 49; 139 | 140 | repeated NestedEnum repeated_nested_enum = 51; 141 | repeated ForeignEnum repeated_foreign_enum = 52; 142 | 143 | repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; 144 | repeated string repeated_cord = 55 [ctype = CORD]; 145 | 146 | // Packed 147 | repeated int32 packed_int32 = 75 [packed = true]; 148 | repeated int64 packed_int64 = 76 [packed = true]; 149 | repeated uint32 packed_uint32 = 77 [packed = true]; 150 | repeated uint64 packed_uint64 = 78 [packed = true]; 151 | repeated sint32 packed_sint32 = 79 [packed = true]; 152 | repeated sint64 packed_sint64 = 80 [packed = true]; 153 | repeated fixed32 packed_fixed32 = 81 [packed = true]; 154 | repeated fixed64 packed_fixed64 = 82 [packed = true]; 155 | repeated sfixed32 packed_sfixed32 = 83 [packed = true]; 156 | repeated sfixed64 packed_sfixed64 = 84 [packed = true]; 157 | repeated float packed_float = 85 [packed = true]; 158 | repeated double packed_double = 86 [packed = true]; 159 | repeated bool packed_bool = 87 [packed = true]; 160 | repeated NestedEnum packed_nested_enum = 88 [packed = true]; 161 | 162 | // Unpacked 163 | repeated int32 unpacked_int32 = 89 [packed = false]; 164 | repeated int64 unpacked_int64 = 90 [packed = false]; 165 | repeated uint32 unpacked_uint32 = 91 [packed = false]; 166 | repeated uint64 unpacked_uint64 = 92 [packed = false]; 167 | repeated sint32 unpacked_sint32 = 93 [packed = false]; 168 | repeated sint64 unpacked_sint64 = 94 [packed = false]; 169 | repeated fixed32 unpacked_fixed32 = 95 [packed = false]; 170 | repeated fixed64 unpacked_fixed64 = 96 [packed = false]; 171 | repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; 172 | repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; 173 | repeated float unpacked_float = 99 [packed = false]; 174 | repeated double unpacked_double = 100 [packed = false]; 175 | repeated bool unpacked_bool = 101 [packed = false]; 176 | repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; 177 | 178 | // Map 179 | map map_int32_int32 = 56; 180 | map map_int64_int64 = 57; 181 | map map_uint32_uint32 = 58; 182 | map map_uint64_uint64 = 59; 183 | map map_sint32_sint32 = 60; 184 | map map_sint64_sint64 = 61; 185 | map map_fixed32_fixed32 = 62; 186 | map map_fixed64_fixed64 = 63; 187 | map map_sfixed32_sfixed32 = 64; 188 | map map_sfixed64_sfixed64 = 65; 189 | map map_int32_float = 66; 190 | map map_int32_double = 67; 191 | map map_bool_bool = 68; 192 | map map_string_string = 69; 193 | map map_string_bytes = 70; 194 | map map_string_nested_message = 71; 195 | map map_string_foreign_message = 72; 196 | map map_string_nested_enum = 73; 197 | map map_string_foreign_enum = 74; 198 | 199 | oneof oneof_field { 200 | uint32 oneof_uint32 = 111; 201 | NestedMessage oneof_nested_message = 112; 202 | string oneof_string = 113; 203 | bytes oneof_bytes = 114; 204 | bool oneof_bool = 115; 205 | uint64 oneof_uint64 = 116; 206 | float oneof_float = 117; 207 | double oneof_double = 118; 208 | NestedEnum oneof_enum = 119; 209 | // FIXME 210 | // google.protobuf.NullValue oneof_null_value = 120; 211 | } 212 | 213 | // Well-known types 214 | google.protobuf.BoolValue optional_bool_wrapper = 201; 215 | google.protobuf.Int32Value optional_int32_wrapper = 202; 216 | google.protobuf.Int64Value optional_int64_wrapper = 203; 217 | google.protobuf.UInt32Value optional_uint32_wrapper = 204; 218 | google.protobuf.UInt64Value optional_uint64_wrapper = 205; 219 | google.protobuf.FloatValue optional_float_wrapper = 206; 220 | google.protobuf.DoubleValue optional_double_wrapper = 207; 221 | google.protobuf.StringValue optional_string_wrapper = 208; 222 | google.protobuf.BytesValue optional_bytes_wrapper = 209; 223 | 224 | repeated google.protobuf.BoolValue repeated_bool_wrapper = 211; 225 | repeated google.protobuf.Int32Value repeated_int32_wrapper = 212; 226 | repeated google.protobuf.Int64Value repeated_int64_wrapper = 213; 227 | repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214; 228 | repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215; 229 | repeated google.protobuf.FloatValue repeated_float_wrapper = 216; 230 | repeated google.protobuf.DoubleValue repeated_double_wrapper = 217; 231 | repeated google.protobuf.StringValue repeated_string_wrapper = 218; 232 | repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219; 233 | 234 | google.protobuf.Duration optional_duration = 301; 235 | google.protobuf.Timestamp optional_timestamp = 302; 236 | google.protobuf.FieldMask optional_field_mask = 303; 237 | // FIXME 238 | // google.protobuf.Struct optional_struct = 304; 239 | google.protobuf.Any optional_any = 305; 240 | // FIXME 241 | // google.protobuf.Value optional_value = 306; 242 | // FIXME 243 | // google.protobuf.NullValue optional_null_value = 307; 244 | 245 | repeated google.protobuf.Duration repeated_duration = 311; 246 | repeated google.protobuf.Timestamp repeated_timestamp = 312; 247 | repeated google.protobuf.FieldMask repeated_fieldmask = 313; 248 | // FIXME 249 | // repeated google.protobuf.Struct repeated_struct = 324; 250 | repeated google.protobuf.Any repeated_any = 315; 251 | // FIXME 252 | // repeated google.protobuf.Value repeated_value = 316; 253 | // FIXME 254 | // repeated google.protobuf.ListValue repeated_list_value = 317; 255 | 256 | // Test field-name-to-JSON-name convention. 257 | // (protobuf says names can be any valid C/C++ identifier.) 258 | int32 fieldname1 = 401; 259 | int32 field_name2 = 402; 260 | int32 _field_name3 = 403; 261 | int32 field__name4_ = 404; 262 | int32 field0name5 = 405; 263 | int32 field_0_name6 = 406; 264 | int32 fieldName7 = 407; 265 | int32 FieldName8 = 408; 266 | int32 field_Name9 = 409; 267 | int32 Field_Name10 = 410; 268 | int32 FIELD_NAME11 = 411; 269 | int32 FIELD_name12 = 412; 270 | int32 __field_name13 = 413; 271 | int32 __Field_name14 = 414; 272 | int32 field__name15 = 415; 273 | int32 field__Name16 = 416; 274 | int32 field_name17__ = 417; 275 | int32 Field_name18__ = 418; 276 | 277 | // Reserved for testing unknown fields 278 | reserved 501 to 510; 279 | } 280 | 281 | message ForeignMessage { 282 | int32 c = 1; 283 | } 284 | 285 | enum ForeignEnum { 286 | FOREIGN_FOO = 0; 287 | FOREIGN_BAR = 1; 288 | FOREIGN_BAZ = 2; 289 | } 290 | 291 | message NullHypothesisProto3 {} 292 | 293 | message EnumOnlyProto3 { 294 | enum Bool { 295 | kFalse = 0; 296 | kTrue = 1; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /examples/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/timestamppb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone or local 44 | // calendar, encoded as a count of seconds and fractions of seconds at 45 | // nanosecond resolution. The count is relative to an epoch at UTC midnight on 46 | // January 1, 1970, in the proleptic Gregorian calendar which extends the 47 | // Gregorian calendar backwards to year one. 48 | // 49 | // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 50 | // second table is needed for interpretation, using a [24-hour linear 51 | // smear](https://developers.google.com/time/smear). 52 | // 53 | // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 54 | // restricting to that range, we ensure that we can convert to and from [RFC 55 | // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 56 | // 57 | // # Examples 58 | // 59 | // Example 1: Compute Timestamp from POSIX `time()`. 60 | // 61 | // Timestamp timestamp; 62 | // timestamp.set_seconds(time(NULL)); 63 | // timestamp.set_nanos(0); 64 | // 65 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 66 | // 67 | // struct timeval tv; 68 | // gettimeofday(&tv, NULL); 69 | // 70 | // Timestamp timestamp; 71 | // timestamp.set_seconds(tv.tv_sec); 72 | // timestamp.set_nanos(tv.tv_usec * 1000); 73 | // 74 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 75 | // 76 | // FILETIME ft; 77 | // GetSystemTimeAsFileTime(&ft); 78 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 79 | // 80 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 81 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 82 | // Timestamp timestamp; 83 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 84 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 85 | // 86 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 87 | // 88 | // long millis = System.currentTimeMillis(); 89 | // 90 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 91 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 92 | // 93 | // 94 | // Example 5: Compute Timestamp from Java `Instant.now()`. 95 | // 96 | // Instant now = Instant.now(); 97 | // 98 | // Timestamp timestamp = 99 | // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 100 | // .setNanos(now.getNano()).build(); 101 | // 102 | // 103 | // Example 6: Compute Timestamp from current time in Python. 104 | // 105 | // timestamp = Timestamp() 106 | // timestamp.GetCurrentTime() 107 | // 108 | // # JSON Mapping 109 | // 110 | // In JSON format, the Timestamp type is encoded as a string in the 111 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 112 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 113 | // where {year} is always expressed using four digits while {month}, {day}, 114 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 115 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 116 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 117 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 118 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 119 | // able to accept both UTC and other timezones (as indicated by an offset). 120 | // 121 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 122 | // 01:30 UTC on January 15, 2017. 123 | // 124 | // In JavaScript, one can convert a Date object to this format using the 125 | // standard 126 | // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 127 | // method. In Python, a standard `datetime.datetime` object can be converted 128 | // to this format using 129 | // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 130 | // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 131 | // the Joda Time's [`ISODateTimeFormat.dateTime()`]( 132 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D 133 | // ) to obtain a formatter capable of generating timestamps in this format. 134 | // 135 | // 136 | message Timestamp { 137 | // Represents seconds of UTC time since Unix epoch 138 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 139 | // 9999-12-31T23:59:59Z inclusive. 140 | int64 seconds = 1; 141 | 142 | // Non-negative fractions of a second at nanosecond resolution. Negative 143 | // second values with fractions must still have non-negative nanos values 144 | // that count forward in time. Must be from 0 to 999,999,999 145 | // inclusive. 146 | int32 nanos = 2; 147 | } 148 | -------------------------------------------------------------------------------- /examples/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | // 36 | // These wrappers have no meaningful use within repeated fields as they lack 37 | // the ability to detect presence on individual elements. 38 | // These wrappers have no meaningful use within a map or a oneof since 39 | // individual entries of a map or fields of a oneof can already detect presence. 40 | 41 | syntax = "proto3"; 42 | 43 | package google.protobuf; 44 | 45 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 46 | option cc_enable_arenas = true; 47 | option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; 48 | option java_package = "com.google.protobuf"; 49 | option java_outer_classname = "WrappersProto"; 50 | option java_multiple_files = true; 51 | option objc_class_prefix = "GPB"; 52 | 53 | // Wrapper message for `double`. 54 | // 55 | // The JSON representation for `DoubleValue` is JSON number. 56 | message DoubleValue { 57 | // The double value. 58 | double value = 1; 59 | } 60 | 61 | // Wrapper message for `float`. 62 | // 63 | // The JSON representation for `FloatValue` is JSON number. 64 | message FloatValue { 65 | // The float value. 66 | float value = 1; 67 | } 68 | 69 | // Wrapper message for `int64`. 70 | // 71 | // The JSON representation for `Int64Value` is JSON string. 72 | message Int64Value { 73 | // The int64 value. 74 | int64 value = 1; 75 | } 76 | 77 | // Wrapper message for `uint64`. 78 | // 79 | // The JSON representation for `UInt64Value` is JSON string. 80 | message UInt64Value { 81 | // The uint64 value. 82 | uint64 value = 1; 83 | } 84 | 85 | // Wrapper message for `int32`. 86 | // 87 | // The JSON representation for `Int32Value` is JSON number. 88 | message Int32Value { 89 | // The int32 value. 90 | int32 value = 1; 91 | } 92 | 93 | // Wrapper message for `uint32`. 94 | // 95 | // The JSON representation for `UInt32Value` is JSON number. 96 | message UInt32Value { 97 | // The uint32 value. 98 | uint32 value = 1; 99 | } 100 | 101 | // Wrapper message for `bool`. 102 | // 103 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 104 | message BoolValue { 105 | // The bool value. 106 | bool value = 1; 107 | } 108 | 109 | // Wrapper message for `string`. 110 | // 111 | // The JSON representation for `StringValue` is JSON string. 112 | message StringValue { 113 | // The string value. 114 | string value = 1; 115 | } 116 | 117 | // Wrapper message for `bytes`. 118 | // 119 | // The JSON representation for `BytesValue` is JSON string. 120 | message BytesValue { 121 | // The bytes value. 122 | bytes value = 1; 123 | } 124 | -------------------------------------------------------------------------------- /examples/group.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | message Grouped { 4 | optional group Data = 201 { 5 | optional int32 group_int32 = 202; 6 | optional uint32 group_uint32 = 203; 7 | } 8 | repeated group Data2 = 211 { 9 | optional int32 group2_int32 = 212; 10 | optional uint32 group2_uint32 = 213; 11 | } 12 | } -------------------------------------------------------------------------------- /examples/map.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | message MapFields { 3 | map map_int32_int32 = 56; 4 | } 5 | -------------------------------------------------------------------------------- /examples/message_with_defaults.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | import "only_enum.proto"; 4 | 5 | message Person { 6 | required string name = 1; 7 | required int32 id = 2; // Unique ID number for this person. 8 | required string email = 3 [default="anon@anon.com"]; 9 | optional SomeKind kind = 4 [default=A]; 10 | } -------------------------------------------------------------------------------- /examples/nested.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | message Test1 { 4 | int32 a = 1; 5 | } 6 | 7 | message Test3 { 8 | Test1 c = 3; 9 | int32 d = 4; 10 | } 11 | 12 | message Test4 { 13 | repeated int32 d = 4 [packed=true]; 14 | } -------------------------------------------------------------------------------- /examples/oneof-2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message TestAllTypesProto3 { 4 | message NestedMessage { 5 | int32 a = 1; 6 | } 7 | enum NestedEnum { 8 | FOO = 0; 9 | BAR = 1; 10 | BAZ = 2; 11 | NEG = -1; // Intentionally negative. 12 | } 13 | 14 | bool optional_bool = 13; 15 | string optional_string = 14; 16 | bytes optional_bytes = 15; 17 | 18 | oneof oneof_field { 19 | uint32 oneof_uint32 = 111; 20 | NestedMessage oneof_nested_message = 112; 21 | string oneof_string = 113; 22 | bytes oneof_bytes = 114; 23 | bool oneof_bool = 115; 24 | uint64 oneof_uint64 = 116; 25 | float oneof_float = 117; 26 | double oneof_double = 118; 27 | NestedEnum oneof_enum = 119; 28 | } 29 | repeated int32 repeated_int32 = 31; 30 | repeated int64 repeated_int64 = 32; 31 | repeated uint32 repeated_uint32 = 33; 32 | } -------------------------------------------------------------------------------- /examples/oneof.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Response { 4 | oneof result { 5 | string error1 = 2; 6 | string error2 = 3; 7 | } 8 | oneof foo { 9 | int32 bar = 4; 10 | int32 baz = 5; 11 | } 12 | } -------------------------------------------------------------------------------- /examples/only_enum.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum SomeKind { 4 | NONE = 0; 5 | A = 1; 6 | B = 2; 7 | C = 3; 8 | } 9 | -------------------------------------------------------------------------------- /examples/person.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | 4 | import "only_enum.proto"; 5 | 6 | message Person { 7 | string name = 1; 8 | int32 id = 2; // Unique ID number for this person. 9 | string email = 3; 10 | SomeKind kind = 4; 11 | } -------------------------------------------------------------------------------- /examples/proto3_optional.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Proto3MsgWithOptional { 4 | optional string hello = 1; 5 | } 6 | -------------------------------------------------------------------------------- /examples/recursive.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // FIXME 4 | // message NonRec { 5 | // Rec rec = 1; 6 | // } 7 | // FIXME 8 | // message Rec { 9 | // Rec rec = 1; 10 | // } -------------------------------------------------------------------------------- /gen/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /script/conformance.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | zig build && 4 | ~/Downloads/protobuf/conformance_test_runner --output_dir gen/conformance zig-out/bin/conformance && 5 | echo "done" 6 | -------------------------------------------------------------------------------- /script/gen-all.sh: -------------------------------------------------------------------------------- 1 | # args should be folders to be generated. usually examples/ 2 | # usage example: $ script/gen-all.sh -I examples examples/ 3 | 4 | set -e 5 | ZIG_FLAGS= #-Dgen-format=c #-Dlog-level=info 6 | zig build $ZIG_FLAGS -freference-trace 7 | DEST_DIR=gen 8 | 9 | # recursively remove all *.pb.zig files from $DEST_DIR 10 | # find $DEST_DIR -name "*.pb.zig" -exec rm {} \; 11 | rm -rf gen/* 12 | 13 | # iterate args, skipping '-I examples' 14 | state="start" 15 | inc="" 16 | for arg in $@; do 17 | if [[ $arg == "-I" ]]; then 18 | state="-I" 19 | elif [[ $state == "-I" ]]; then 20 | state="" 21 | inc=$arg 22 | else 23 | PROTOFILES=$(find $arg -name "*.proto") 24 | 25 | for file in $PROTOFILES; do 26 | script/gen.sh -I $inc $file 27 | done 28 | 29 | for file in $PROTOFILES; do 30 | script/gen-test.sh -I $inc $file 31 | done 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /script/gen-protobuf-c.sh: -------------------------------------------------------------------------------- 1 | # generate a protobuf-c .h/.c files from a .proto file 2 | # args: -I examples/ examples/only_message.proto 3 | 4 | protobuf_c_dir=../../c/protobuf-c 5 | $protobuf_c_dir/build/bin/protoc-c --c_out=$protobuf_c_dir/gen $@ 6 | echo $protobuf_c_dir/gen/"${@: -1}" 7 | -------------------------------------------------------------------------------- /script/gen-test.sh: -------------------------------------------------------------------------------- 1 | # args: -I examples examples/file.proto 2 | # set -ex 3 | DEST_DIR=gen 4 | 5 | # iterate args, skipping '-I examples' 6 | state="start" 7 | inc="" 8 | 9 | for arg in $@; do 10 | if [[ $arg == "-I" ]]; then 11 | state="-I" 12 | elif [[ $state == "-I" ]]; then 13 | state="" 14 | inc=$arg 15 | else 16 | arg=${arg#$inc/} # remove $inc/ prefix 17 | FILE="$DEST_DIR/${arg%.*}.pb.zig" # replace the extension, add $DEST_DIR prefix 18 | # CMD="zig test $FILE --pkg-begin protobuf src/lib.zig --pkg-begin protobuf src/lib.zig --pkg-end --pkg-end -freference-trace --main-pkg-path ." 19 | CMD="zig test --dep protobuf -Mroot=$FILE --dep protobuf -Mprotobuf=src/lib.zig -freference-trace" 20 | echo $CMD 21 | $($CMD) 22 | # build generated c code 23 | # FILE="$DEST_DIR/${arg%.*}.pb.c" # replace the extension, add $DEST_DIR prefix 24 | # CMD="zig build-lib -lc -I$DEST_DIR $FILE -femit-bin=/tmp/tmp.a" 25 | # echo $CMD 26 | # $($CMD) 27 | fi 28 | done 29 | -------------------------------------------------------------------------------- /script/gen.sh: -------------------------------------------------------------------------------- 1 | # args: -I examples examples/file.proto 2 | # set -ex 3 | DEST_DIR=gen 4 | 5 | dir=${dir%/*} 6 | CMD="zig-out/bin/protoc --plugin=zig-out/bin/protoc-gen-zig --zig_out=$DEST_DIR $@" 7 | echo $CMD 8 | $($CMD) 9 | -------------------------------------------------------------------------------- /script/protoc-capture.sh: -------------------------------------------------------------------------------- 1 | # args are optional protopath and protofile 2 | # use system protoc to encode a CodeGeneratorRequest to zig-out/bin/protoc-gen-zig 3 | 4 | set -e 5 | zig build 6 | protoc --plugin=./zig-out/bin/protoc-gen-zig --zig_out=gen $@ -------------------------------------------------------------------------------- /script/protoc-decode-text.sh: -------------------------------------------------------------------------------- 1 | # use system protoc to decode a CodeGeneratorRequest 2 | 3 | set -e 4 | INCLUDE_DIR=~/Downloads/protobuf/src 5 | protoc -I $INCLUDE_DIR --decode=google.protobuf.compiler.CodeGeneratorRequest $INCLUDE_DIR/google/protobuf/compiler/plugin.proto 6 | -------------------------------------------------------------------------------- /script/protoc-enc-dec.sh: -------------------------------------------------------------------------------- 1 | # use system protoc to parse a proto and then decode it to text format 2 | 3 | set -ex 4 | script/protoc-capture.sh $@ |& script/protoc-decode-text.sh -------------------------------------------------------------------------------- /script/protoc-enc-zig-dec.sh: -------------------------------------------------------------------------------- 1 | # use system protoc to parse a proto and then decode it to text format 2 | 3 | set -ex 4 | script/protoc-capture.sh $@ |& script/zig-decode-text.sh -------------------------------------------------------------------------------- /script/protoc-gen-protoc-c.sh: -------------------------------------------------------------------------------- 1 | set -x 2 | pushd ../../c/protobuf-c/ 3 | GENDIR=gen2 4 | PATH=build/bin:$PATH protoc --c_out=$GENDIR -I ~/Downloads/protobuf/src/google/ ~/Downloads/protobuf/src/google/protobuf/descriptor.proto 5 | pwd 6 | ls $GENDIR 7 | popd -------------------------------------------------------------------------------- /script/test-all.sh: -------------------------------------------------------------------------------- 1 | zig build test && script/gen-all.sh -I examples examples/ -------------------------------------------------------------------------------- /script/test-single.sh: -------------------------------------------------------------------------------- 1 | set -x 2 | zig test "$@" --mod protobuf:protobuf:src/lib.zig --deps protobuf -freference-trace --main-pkg-path . -------------------------------------------------------------------------------- /script/zig-decode-text.sh: -------------------------------------------------------------------------------- 1 | # build and run src/main.zig which is a driver for message deserialization 2 | # it expects to read a protobuf message from stdin 3 | 4 | set -e 5 | INCLUDE_DIR=~/Downloads/protobuf/src 6 | zig build run -freference-trace=10 -Dlog-level=info -- -I $INCLUDE_DIR --decode=google.protobuf.compiler.CodeGeneratorRequest $INCLUDE_DIR/google/protobuf/compiler/plugin.proto 7 | -------------------------------------------------------------------------------- /sdk.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const plugin_arg = "--plugin=zig-out/bin/protoc-gen-zig" ++ 4 | (if (@import("builtin").target.os.tag == .windows) ".exe" else ""); 5 | 6 | pub const GenStep = struct { 7 | step: std.Build.Step, 8 | b: *std.Build, 9 | sources: std.ArrayListUnmanaged(std.Build.GeneratedFile) = .{}, 10 | cache_path: []const u8, 11 | lib_file: std.Build.GeneratedFile, 12 | module: *std.Build.Module, 13 | 14 | /// init a GenStep, create zig-cache/protobuf-zig if not exists, setup 15 | /// dependencies, and setup args to exe.run() 16 | pub fn create( 17 | b: *std.Build, 18 | protoc_filename: []const u8, 19 | exe: *std.Build.Step.Compile, 20 | files: []const []const u8, 21 | ) !*GenStep { 22 | const self = b.allocator.create(GenStep) catch unreachable; 23 | const cache_root = std.fs.path.basename(b.cache_root.path orelse "."); 24 | const protobuf_zig_path = "protobuf-zig"; 25 | const cache_path = try std.fs.path.join( 26 | b.allocator, 27 | &.{ cache_root, protobuf_zig_path }, 28 | ); 29 | const lib_path = try std.fs.path.join( 30 | b.allocator, 31 | &.{ cache_path, "lib.zig" }, 32 | ); 33 | self.* = GenStep{ 34 | .step = std.Build.Step.init(.{ 35 | .id = .custom, 36 | .name = "build-template", 37 | .owner = b, 38 | .makeFn = &make, 39 | }), 40 | .b = b, 41 | .cache_path = cache_path, 42 | .lib_file = .{ 43 | .step = &self.step, 44 | .path = lib_path, 45 | }, 46 | .module = b.createModule(.{ .root_source_file = b.path(lib_path) }), 47 | }; 48 | 49 | for (files) |file| { 50 | const source = try self.sources.addOne(b.allocator); 51 | source.* = .{ .path = file, .step = &self.step }; 52 | } 53 | 54 | const run_cmd = b.addSystemCommand(&.{ 55 | b.getInstallPath(.bin, protoc_filename), 56 | plugin_arg, 57 | "--zig_out", 58 | cache_path, 59 | "-I", 60 | "examples", 61 | }); 62 | run_cmd.step.dependOn(&exe.step); 63 | for (files) |file| run_cmd.addArg(file); 64 | self.step.dependOn(&run_cmd.step); 65 | 66 | try b.cache_root.handle.makePath(protobuf_zig_path); 67 | 68 | return self; 69 | } 70 | 71 | /// creates a 'lib.zig' file at self.lib_file.path which exports all 72 | /// generated .pb.zig files 73 | fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void { 74 | const self: *GenStep = @fieldParentPtr("step", step); 75 | 76 | var file = try std.fs.cwd().createFile(self.lib_file.path.?, .{}); 77 | defer file.close(); 78 | const writer = file.writer(); 79 | for (self.sources.items) |source| { 80 | const endidx = std.mem.lastIndexOf(u8, source.path.?, ".proto") orelse { 81 | std.log.err( 82 | "invalid path '{s}'. expected to end with '.proto'", 83 | .{source.path.?}, 84 | ); 85 | return error.InvalidPath; 86 | }; 87 | const startidx = if (std.mem.lastIndexOfScalar( 88 | u8, 89 | source.path.?[0..endidx], 90 | '/', 91 | )) |i| i + 1 else 0; 92 | const name = source.path.?[startidx..endidx]; 93 | // remove illegal characters to make a zig identifier 94 | var buf: [256]u8 = undefined; 95 | @memcpy(buf[0..name.len], name); 96 | if (!std.ascii.isAlphabetic(name[0]) and name[0] != '_') { 97 | std.log.err( 98 | "invalid identifier '{s}'. filename must start with alphabetic or underscore", 99 | .{name}, 100 | ); 101 | return error.InvalidIdentifier; 102 | } 103 | for (name[1..], 0..) |c, i| { 104 | if (!std.ascii.isAlphanumeric(c)) buf[i + 1] = '_'; 105 | } 106 | const path = if (std.mem.startsWith(u8, source.path.?, "examples/")) 107 | source.path.?[0..endidx]["examples/".len..] 108 | else 109 | source.path.?[0..endidx]; 110 | try writer.print( 111 | \\pub const {s} = @import("{s}.pb.zig"); 112 | \\ 113 | , .{ buf[0..name.len], path }); 114 | } 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /src/common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | 4 | pub const log = if (@import("builtin").is_test) 5 | std.log.scoped(.@"protobuf-zig") 6 | else 7 | struct { 8 | pub const debug = dummy_log; 9 | pub const info = dummy_log; 10 | pub const warn = dummy_log; 11 | pub const err = std.log.err; 12 | fn dummy_log( 13 | comptime format: []const u8, 14 | args: anytype, 15 | ) void { 16 | _ = args; 17 | _ = format; 18 | } 19 | }; 20 | 21 | pub const GenFormat = enum { zig, c }; 22 | pub const panicf = std.debug.panic; 23 | pub fn ptrAlign(comptime Ptr: type) comptime_int { 24 | return @typeInfo(Ptr).pointer.alignment; 25 | } 26 | 27 | pub fn ptrAlignCast(comptime Ptr: type, ptr: anytype) Ptr { 28 | return @ptrCast(@alignCast(ptr)); 29 | } 30 | 31 | pub fn ptrfmt(ptr: anytype) PtrFmt { 32 | return .{ .ptr = @intFromPtr(ptr) }; 33 | } 34 | 35 | pub const PtrFmt = struct { 36 | ptr: usize, 37 | 38 | pub fn format(value: PtrFmt, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 39 | try writer.print("@{x}", .{value.ptr}); 40 | } 41 | }; 42 | 43 | pub fn firstNBytes(s: []const u8, n: usize) []const u8 { 44 | return s[0..@min(s.len, n)]; 45 | } 46 | pub fn afterLastIndexOf(s: []const u8, delimeter: u8) []const u8 { 47 | const start = if (mem.lastIndexOfScalar(u8, s, delimeter)) |i| i + 1 else 0; 48 | return s[start..]; 49 | } 50 | /// split on last instance of 'delimeter' 51 | pub fn splitOn(comptime T: type, s: T, delimeter: std.meta.Child(T)) [2]T { 52 | const start = if (mem.lastIndexOfScalar(std.meta.Child(T), s, delimeter)) |i| i else 0; 53 | return [2]T{ s[0..start], s[start + 1 ..] }; 54 | } 55 | pub fn todo(comptime fmt: []const u8, args: anytype) noreturn { 56 | panicf("TODO " ++ fmt, args); 57 | } 58 | pub fn compileErr(comptime fmt: []const u8, args: anytype) noreturn { 59 | @compileError(std.fmt.comptimePrint(fmt, args)); 60 | } 61 | -------------------------------------------------------------------------------- /src/conformance.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | 4 | const generated = @import("generated"); 5 | const cf = generated.conformance; 6 | const test3 = generated.test_messages_proto3; 7 | const test2 = generated.test_messages_proto2; 8 | const Request = cf.ConformanceRequest; 9 | const Response = cf.ConformanceResponse; 10 | const pb = @import("protobuf"); 11 | const String = pb.extern_types.String; 12 | 13 | pub const std_options = std.Options{ 14 | .log_level = std.meta.stringToEnum(std.log.Level, @tagName(@import("build_options").log_level)).?, 15 | }; 16 | 17 | pub fn main() !void { 18 | var total_runs: usize = 0; 19 | while (true) { 20 | const is_done = serveConformanceRequest() catch |e| { 21 | std.debug.panic("fatal: {s}", .{@errorName(e)}); 22 | }; 23 | if (is_done) break; 24 | total_runs += 1; 25 | } 26 | std.debug.print("conformance-zig: received EOF from test runner after {} tests\n", .{total_runs}); 27 | } 28 | 29 | fn serializeTo(serializable: anytype, writer: anytype) !void { 30 | var countwriter = std.io.countingWriter(std.io.null_writer); 31 | try pb.protobuf.serialize(&serializable.base, countwriter.writer()); 32 | try writer.writeInt(u32, @as(u32, @intCast(countwriter.bytes_written)), .little); 33 | try pb.protobuf.serialize(&serializable.base, writer); 34 | } 35 | 36 | fn debugReq(request: *Request, buf: []const u8) void { 37 | const tag = request.activeTag(.payload) orelse unreachable; 38 | const payload = switch (tag) { 39 | .payload__protobuf_payload => request.payload.protobuf_payload, 40 | .payload__json_payload => request.payload.json_payload, 41 | .payload__jspb_payload => request.payload.jspb_payload, 42 | .payload__text_payload => request.payload.text_payload, 43 | else => unreachable, 44 | }; 45 | 46 | const payload_tagname = switch (tag) { 47 | .payload__protobuf_payload => "protobuf_payload", 48 | .payload__json_payload => "json_payload", 49 | .payload__jspb_payload => "jspb_payload", 50 | .payload__text_payload => "text_payload", 51 | else => unreachable, 52 | }; 53 | std.debug.print("----\n", .{}); 54 | std.debug.print("message_type {s}\n", .{request.message_type}); 55 | std.debug.print("requested_output_format {}\n", .{request.requested_output_format}); 56 | std.debug.print("test_category {}\n", .{request.test_category}); 57 | if (tag == .payload__protobuf_payload) 58 | std.debug.print("payload {s} : {}({})\n", .{ payload_tagname, std.fmt.fmtSliceHexLower(payload.slice()), payload.len }) 59 | else 60 | std.debug.print("payload {s} : {s}({})\n", .{ payload_tagname, payload.slice(), payload.len }); 61 | std.debug.print("message {}\n", .{std.fmt.fmtSliceHexLower(buf)}); 62 | std.debug.print("----\n", .{}); 63 | } 64 | 65 | fn serveConformanceRequest() !bool { 66 | const stdin = std.io.getStdIn().reader(); 67 | const stdout = std.io.getStdOut().writer(); 68 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 69 | const allr = arena.allocator(); 70 | var buf = std.ArrayList(u8).init(allr); 71 | 72 | const in_len = stdin.readInt(u32, .little) catch |err| return switch (err) { 73 | error.EndOfStream => true, 74 | else => err, 75 | }; 76 | 77 | const debug_request = false; 78 | if (debug_request) std.debug.print("\nin_len {} ", .{in_len}); 79 | try buf.ensureTotalCapacity(in_len); 80 | buf.items.len = in_len; 81 | const amt = try stdin.read(buf.items); 82 | if (amt != in_len) return error.Amt; 83 | // if (debug_request) std.debug.print("message {}\n", .{std.fmt.fmtSliceHexLower(buf.items)}); 84 | // std.debug.print("Request {}\n", .{Request}); 85 | 86 | var ctx = pb.protobuf.context(buf.items, allr); 87 | var request_m = try ctx.deserialize(&Request.descriptor); 88 | const request = try request_m.as(Request); 89 | 90 | const response = runTest(allr, request) catch |e| { 91 | std.debug.print("error: runTest() {s}\n", .{@errorName(e)}); 92 | return e; 93 | }; 94 | if (debug_request) { 95 | if (response.activeTag(.result)) |tag| switch (tag) { 96 | .result__runtime_error => { 97 | std.debug.print("runtime_error: {s}\n", .{response.result.runtime_error}); 98 | debugReq(request, buf.items); 99 | }, 100 | .result__serialize_error => { 101 | std.debug.print("serialize_error: {s}\n", .{response.result.serialize_error}); 102 | debugReq(request, buf.items); 103 | }, 104 | .result__parse_error => { 105 | std.debug.print("parse_error: {s}\n", .{response.result.parse_error}); 106 | debugReq(request, buf.items); 107 | }, 108 | else => {}, 109 | }; 110 | } 111 | try serializeTo(response, stdout); 112 | return false; 113 | } 114 | 115 | fn runTest(allr: Allocator, request: *Request) !Response { 116 | var response = Response.init(); 117 | // if (request.test_category == .JSON_TEST) { 118 | // debugReq(request, &.{}); 119 | // } 120 | if (std.mem.eql(u8, request.message_type.slice(), "conformance.FailureSet")) { 121 | var failure_set = cf.FailureSet.init(); 122 | const all_failures: []const []const u8 = &.{ 123 | // list of known failing tests to skip 124 | "Required.DurationProtoInputTooLarge.JsonOutput", 125 | "Required.DurationProtoInputTooSmall.JsonOutput", 126 | "Required.Proto2.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput", 127 | "Required.Proto2.ProtobufInput.ValidDataMap.STRING.MESSAGE.MergeValue.ProtobufOutput", 128 | "Required.Proto2.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput", 129 | "Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.JsonOutput", 130 | "Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput", 131 | "Required.Proto3.ProtobufInput.ValidDataMap.STRING.ENUM.MissingDefault.JsonOutput", 132 | "Required.Proto3.ProtobufInput.ValidDataMap.STRING.MESSAGE.MergeValue.JsonOutput", 133 | "Required.Proto3.ProtobufInput.ValidDataMap.STRING.MESSAGE.MergeValue.ProtobufOutput", 134 | "Required.Proto3.ProtobufInput.ValidDataMap.STRING.MESSAGE.MissingDefault.JsonOutput", 135 | "Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.JsonOutput", 136 | "Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput", 137 | "Required.TimestampProtoInputTooLarge.JsonOutput", 138 | "Required.TimestampProtoInputTooSmall.JsonOutput", 139 | }; 140 | 141 | for (all_failures) |f| { 142 | try failure_set.failure.append(allr, String.init(f)); 143 | } 144 | var output = std.ArrayList(u8).init(allr); 145 | try pb.protobuf.serialize(&failure_set.base, output.writer()); 146 | if (all_failures.len == 0) 147 | response.set(.result__skipped, String.init("Empty failure set")) 148 | else { 149 | failure_set.setPresent(.failure); 150 | response.set(.result__protobuf_payload, String.init(output.items)); 151 | } 152 | return response; 153 | } else { 154 | var test_message: ?*pb.types.Message = null; 155 | if (request.activeTag(.payload)) |tag| switch (tag) { 156 | .payload__protobuf_payload => { 157 | var ctx = pb.protobuf.context(request.payload.protobuf_payload.slice(), allr); 158 | test_message = ctx.deserialize(&test3.TestAllTypesProto3.descriptor) catch |e| switch (e) { 159 | error.EndOfStream => { 160 | response.set(.result__parse_error, String.init("EOF")); 161 | return response; 162 | }, 163 | else => { 164 | // std.debug.print("test_message.deserialize error {s}\n", .{@errorName(e)}); 165 | response.set(.result__parse_error, String.init(@errorName(e))); 166 | return response; 167 | }, 168 | }; 169 | }, 170 | .payload__json_payload => { 171 | response.set(.result__skipped, String.init("TODO json_payload")); 172 | // var tokens = std.json.TokenStream.init(request.payload.json_payload); 173 | // test_message = try std.json.parse(test3.TestAllTypesProto3, &tokens, .{ .ignore_unknown_fields = true }); 174 | return response; 175 | }, 176 | .payload__jspb_payload => { 177 | response.set(.result__skipped, String.init("TODO jspb_payload")); 178 | return response; 179 | }, 180 | .payload__text_payload => { 181 | response.set(.result__skipped, String.init("TODO text_payload")); 182 | return response; 183 | }, 184 | else => unreachable, 185 | }; 186 | switch (request.requested_output_format) { 187 | .UNSPECIFIED => return error.InvalidArgument_UnspecifiedOutputFormat, 188 | .PROTOBUF => { 189 | // response.set(.result__skipped, String.init("TODO PB output")); 190 | var output = std.ArrayList(u8).init(allr); 191 | try pb.protobuf.serialize(test_message.?, output.writer()); 192 | response.set(.result__protobuf_payload, String.init(try output.toOwnedSlice())); 193 | }, 194 | .JSON => { 195 | // response.set(.result__skipped, String.init("TODO JSON output")); 196 | var output = std.ArrayList(u8).init(allr); 197 | try pb.json.serialize(test_message.?, output.writer(), .{}); 198 | response.set( 199 | .result__json_payload, 200 | String.init(try output.toOwnedSlice()), 201 | ); 202 | }, 203 | .JSPB => { 204 | response.set(.result__skipped, String.init("TODO JSPB output")); 205 | }, 206 | .TEXT_FORMAT => { 207 | response.set(.result__skipped, String.init("TODO TEXT_FORMAT output")); 208 | }, 209 | } 210 | } 211 | 212 | return response; 213 | } 214 | -------------------------------------------------------------------------------- /src/extern-types.zig: -------------------------------------------------------------------------------- 1 | //! structures which can be used in extern structs 2 | //! includes String, ArrayList, ArrayListMut 3 | 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const assert = std.debug.assert; 7 | const ptrfmt = common.ptrfmt; 8 | const pb = @import("protobuf"); 9 | const common = pb.common; 10 | 11 | const extern_types = @This(); 12 | 13 | // comment/uncomment this decl to toggle 14 | // const fmtdebug = true; 15 | 16 | /// an extern slice of bytes 17 | pub const String = extern struct { 18 | len: usize, 19 | items: [*]const u8, 20 | 21 | var empty_arr = "".*; 22 | pub const empty: String = String.init(&empty_arr); 23 | 24 | pub fn init(s: []const u8) String { 25 | return .{ .items = s.ptr, .len = s.len }; 26 | } 27 | pub fn initEmpty() String { 28 | return empty; 29 | } 30 | pub fn deinit(s: String, allocator: mem.Allocator) void { 31 | if (s.len != 0 and s.items != empty.items) 32 | allocator.free(s.items[0..s.len]); 33 | } 34 | pub fn slice(s: String) []const u8 { 35 | return s.items[0..s.len]; 36 | } 37 | pub const format = if (@hasDecl(extern_types, "fmtdebug")) 38 | formatDebug 39 | else 40 | formatStandard; 41 | pub fn formatDebug( 42 | s: String, 43 | comptime _: []const u8, 44 | _: std.fmt.FormatOptions, 45 | writer: anytype, 46 | ) !void { 47 | try writer.print("{*}/{}-", .{ s.items, s.len }); 48 | } 49 | pub fn formatStandard( 50 | s: String, 51 | comptime _: []const u8, 52 | _: std.fmt.FormatOptions, 53 | writer: anytype, 54 | ) !void { 55 | if (s.len > 0) try writer.print("{s}", .{s.slice()}); 56 | } 57 | }; 58 | 59 | /// helper for repeated message types. 60 | /// checks that T is a pointer to struct and not pointer to String. 61 | /// returns types.ArrayListMut(T) 62 | pub fn ListMut(comptime T: type) type { 63 | const tinfo = @typeInfo(T); 64 | assert(tinfo == .pointer); 65 | const Child = tinfo.pointer.child; 66 | const cinfo = @typeInfo(Child); 67 | assert(cinfo == .@"struct"); 68 | assert(Child != String); 69 | return ArrayListMut(T); 70 | } 71 | 72 | pub fn isContainer(comptime T: type) bool { 73 | return switch (@typeInfo(T)) { 74 | .@"struct", .@"union", .@"opaque" => true, 75 | else => false, 76 | }; 77 | } 78 | 79 | /// helper for repeated scalar types. 80 | /// checks that T is a String non container type. 81 | /// returns ArrayList(T) 82 | pub fn ListMutScalar(comptime T: type) type { 83 | assert(T == String or !isContainer(T)); 84 | return ArrayListMut(T); 85 | } 86 | 87 | /// similar to std.ArrayList but can be used in extern structs 88 | pub fn ArrayListMut(comptime T: type) type { 89 | return extern struct { 90 | len: usize = 0, 91 | cap: usize = 0, 92 | items: [*]T = undefined, 93 | 94 | pub usingnamespace ListMixins(T, @This(), []T); 95 | }; 96 | } 97 | 98 | /// similar to std.ArrayList but can be used in extern structs. 99 | /// a const version of ArrayListMut. 100 | pub fn ArrayList(comptime T: type) type { 101 | return extern struct { 102 | len: usize = 0, 103 | cap: usize = 0, 104 | items: [*]const T = undefined, 105 | 106 | pub usingnamespace ListMixins(T, @This(), []const T); 107 | }; 108 | } 109 | 110 | pub fn ListMixins(comptime T: type, comptime Self: type, comptime Slice: type) type { 111 | return extern struct { 112 | pub const Child = T; 113 | pub const Ptr = std.meta.fieldInfo(Self, .items).type; 114 | pub const alignment = common.ptrAlign(Ptr); 115 | 116 | pub fn init(items: Slice) Self { 117 | return .{ .items = items.ptr, .len = items.len, .cap = items.len }; 118 | } 119 | pub fn deinit(l: Self, allocator: mem.Allocator) void { 120 | allocator.free(l.items[0..l.cap]); 121 | } 122 | 123 | pub fn slice(self: Self) Slice { 124 | return if (self.len > 0) self.items[0..self.len] else &.{}; 125 | } 126 | 127 | pub const format = if (@hasDecl(extern_types, "fmtdebug")) formatDebug else formatStandard; 128 | 129 | pub fn formatDebug(l: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 130 | try writer.print("{}/0x{x}/0x{}", .{ ptrfmt(l.items), l.len, l.cap }); 131 | _ = try writer.write("...}"); 132 | } 133 | pub fn formatStandard(l: Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 134 | // try writer.print("{}/{}/{}", .{ ptrfmt(l.items), l.len, l.cap }); 135 | _ = try writer.write("{"); 136 | if (l.len != 0) { 137 | for (l.slice(), 0..) |it, i| { 138 | if (i != 0) _ = try writer.write(", "); 139 | try writer.print("{}", .{it}); 140 | } 141 | } 142 | _ = try writer.write("}"); 143 | } 144 | 145 | pub fn addOne(l: *Self, allocator: mem.Allocator) !*T { 146 | try l.ensureTotalCapacity(allocator, l.len + 1); 147 | defer l.len += 1; 148 | return &l.items[l.len]; 149 | } 150 | 151 | pub fn addOneAssumeCapacity(l: *Self) *T { 152 | defer l.len += 1; 153 | return &l.items[l.len]; 154 | } 155 | 156 | pub fn append(l: *Self, allocator: mem.Allocator, item: T) !void { 157 | const ptr = try l.addOne(allocator); 158 | ptr.* = item; 159 | } 160 | 161 | pub fn appendAssumeCapacity(l: *Self, item: T) void { 162 | const ptr = l.addOneAssumeCapacity(); 163 | ptr.* = item; 164 | } 165 | 166 | pub fn appendSlice(l: *Self, allocator: mem.Allocator, items: []const T) !void { 167 | try l.ensureTotalCapacity(allocator, l.len + items.len); 168 | try l.appendSliceAssumeCapacity(items); 169 | } 170 | 171 | pub fn appendSliceAssumeCapacity(l: *Self, items: []const T) !void { 172 | const old_len = l.len; 173 | const new_len = old_len + items.len; 174 | assert(new_len <= l.cap); 175 | l.len = new_len; 176 | @memcpy(l.items[old_len..new_len], items); 177 | } 178 | 179 | pub fn ensureTotalCapacity(l: *Self, allocator: mem.Allocator, new_cap: usize) !void { 180 | if (l.cap >= new_cap) return; 181 | if (l.cap == 0) { 182 | const items = try allocator.alignedAlloc(T, alignment, new_cap); 183 | l.items = items.ptr; 184 | l.cap = new_cap; 185 | } else { 186 | const old_memory = l.slice(); 187 | if (allocator.resize(old_memory, new_cap)) { 188 | l.cap = new_cap; 189 | } else { 190 | const new_items = try allocator.alignedAlloc(T, alignment, new_cap); 191 | @memcpy(new_items[0..l.len], l.slice()); 192 | allocator.free(old_memory); 193 | l.items = new_items.ptr; 194 | l.cap = new_items.len; 195 | } 196 | } 197 | } 198 | const Fmt = struct { 199 | fmt: common.PtrFmt, 200 | len: usize, 201 | cap: usize, 202 | pub fn format(f: Fmt, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 203 | try writer.print("{}/{}/{}", .{ f.fmt, f.len, f.cap }); 204 | } 205 | }; 206 | pub fn inspect(l: Self) Fmt { 207 | return .{ .fmt = ptrfmt(l.items), .len = l.len, .cap = l.cap }; 208 | } 209 | }; 210 | } 211 | 212 | const testing = std.testing; 213 | var tarena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 214 | const talloc = tarena.allocator(); 215 | 216 | test "ArrayListMut" { 217 | const L = ArrayListMut; 218 | const count = 5; 219 | const xs = [1]void{{}} ** count; 220 | var as = L(L(L(L(u8)))){}; 221 | for (xs) |_| { 222 | var bs = L(L(L(u8))){}; 223 | for (xs) |_| { 224 | var cs = L(L(u8)){}; 225 | for (xs) |_| { 226 | var ds = L(u8){}; 227 | for (xs) |_| try ds.append(talloc, 0); 228 | try cs.append(talloc, ds); 229 | } 230 | try bs.append(talloc, cs); 231 | } 232 | try as.append(talloc, bs); 233 | } 234 | try testing.expectEqual(xs.len, as.len); 235 | for (as.slice()) |a| { 236 | try testing.expectEqual(xs.len, a.len); 237 | for (a.slice()) |b| { 238 | try testing.expectEqual(xs.len, b.len); 239 | for (b.slice()) |c| 240 | try testing.expectEqual(xs.len, c.len); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/gen.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const assert = std.debug.assert; 4 | const pb = @import("protobuf"); 5 | const common = pb.common; 6 | const types = pb.types; 7 | const todo = common.todo; 8 | const plugin = pb.plugin; 9 | const descr = pb.descriptor; 10 | const CodeGeneratorRequest = plugin.CodeGeneratorRequest; 11 | const CodeGeneratorResponse = plugin.CodeGeneratorResponse; 12 | const DescriptorProto = descr.DescriptorProto; 13 | const EnumDescriptorProto = descr.EnumDescriptorProto; 14 | const FileDescriptorProto = descr.FileDescriptorProto; 15 | const FieldDescriptorProto = descr.FieldDescriptorProto; 16 | const OneofDescriptorProto = plugin.OneofDescriptorProto; 17 | const FieldDescriptor = types.FieldDescriptor; 18 | const EnumDescriptor = types.EnumDescriptor; 19 | const extern_types = pb.extern_types; 20 | const String = extern_types.String; 21 | const top_level = @This(); 22 | const genc = @import("gen-c.zig"); 23 | const genzig = @import("gen-zig.zig"); 24 | const log = common.log; 25 | 26 | const output_format = @import("build_options").output_format; 27 | 28 | pub const generator = if (output_format == .zig) 29 | genzig 30 | else 31 | genc; 32 | 33 | pub const GenError = error{ 34 | MissingDependency, 35 | MissingProtoFile, 36 | MissingMessageName, 37 | }; 38 | 39 | pub fn genErr(comptime fmt: []const u8, args: anytype, err: anyerror) anyerror { 40 | log.err(fmt, args); 41 | return err; 42 | } 43 | 44 | pub fn context( 45 | alloc: mem.Allocator, 46 | req: *const CodeGeneratorRequest, 47 | ) Context { 48 | return .{ 49 | .alloc = alloc, 50 | .req = req, 51 | }; 52 | } 53 | 54 | pub const Node = union(enum) { 55 | enum_: *const EnumDescriptorProto, 56 | message: *const DescriptorProto, 57 | named: String, 58 | 59 | const Tag = std.meta.Tag(Node); 60 | 61 | pub fn name(n: Node) String { 62 | return switch (n) { 63 | .enum_ => |enum_| enum_.name, 64 | .message => |message| message.name, 65 | .named => |s| s, 66 | }; 67 | } 68 | }; 69 | 70 | pub const Context = struct { 71 | alloc: mem.Allocator, 72 | req: *const CodeGeneratorRequest, 73 | buf: [256]u8 = undefined, 74 | /// map from req.proto_file.(file)name to req.proto_file 75 | depmap: std.StringHashMapUnmanaged(*const FileDescriptorProto) = .{}, 76 | /// map from child (enum/message pointer) to parent message. 77 | /// only includes nested types which have a parent - top level are excluded. 78 | parents: std.AutoHashMapUnmanaged(*const anyopaque, *const DescriptorProto) = .{}, 79 | output: std.ArrayListUnmanaged(u8) = .{}, 80 | enum_buf: std.ArrayListUnmanaged(i32) = .{}, 81 | 82 | pub fn gen(ctx: *Context) !CodeGeneratorResponse { 83 | defer ctx.deinit(); 84 | return top_level.gen(ctx); 85 | } 86 | 87 | pub fn deinit(ctx: *Context) void { 88 | ctx.depmap.deinit(ctx.alloc); 89 | ctx.parents.deinit(ctx.alloc); 90 | ctx.enum_buf.deinit(ctx.alloc); 91 | } 92 | }; 93 | 94 | pub const isUpper = std.ascii.isUpper; 95 | pub const toLower = std.ascii.toLower; 96 | pub const toUpper = std.ascii.toUpper; 97 | 98 | /// convert from camelCase to snake_case 99 | fn writeSnakeCase(writer: anytype, name: String) !void { 100 | var was_upper = true; 101 | for (name.slice()) |c| { 102 | const is_upper = isUpper(c); 103 | if (is_upper) { 104 | if (!was_upper) 105 | _ = try writer.write("_"); 106 | _ = try writer.writeByte(toLower(c)); 107 | } else { 108 | _ = try writer.writeByte(c); 109 | } 110 | was_upper = is_upper; 111 | } 112 | } 113 | 114 | /// convert from camelCase to TITLE_CASE 115 | pub fn writeTitleCase(writer: anytype, name: String) !void { 116 | var was_upper = true; 117 | for (name.slice()) |c| { 118 | const is_upper = isUpper(c); 119 | if (is_upper) { 120 | if (!was_upper) 121 | _ = try writer.write("_"); 122 | _ = try writer.writeByte(c); 123 | } else { 124 | _ = try writer.writeByte(toUpper(c)); 125 | } 126 | was_upper = is_upper; 127 | } 128 | } 129 | 130 | fn normalizePackageName(package_name: []u8) void { 131 | for (package_name, 0..) |c, i| { 132 | if (c == '.' or c == '-' or c == '/') 133 | package_name[i] = '_'; 134 | } 135 | } 136 | 137 | const ImportInfo = struct { 138 | ident: []const u8, 139 | path: []const u8, 140 | is_keyword: bool, 141 | dotdot_count: usize, 142 | }; 143 | 144 | pub fn importInfo( 145 | dep: String, 146 | proto_file: *const FileDescriptorProto, 147 | ) !ImportInfo { 148 | // turn /a/b/c.proto info 'const c = @import("/a/b/c.pb.zig");' 149 | const last_dot_i = mem.lastIndexOfScalar(u8, dep.slice(), '.') orelse 150 | dep.len; 151 | // if proto_file.name() is /a/b/c and dep is /a/b/d, remove leading /a/b/ 152 | // from the import path 153 | const last_slash_i = if (mem.lastIndexOfScalar(u8, dep.slice(), '/')) |i| 154 | i + 1 155 | else 156 | 0; 157 | // discover and remove common path 158 | var i: usize = 0; 159 | const max = @min(last_slash_i, proto_file.name.len); 160 | while (i < max and dep.items[i] == proto_file.name.items[i]) : (i += 1) {} 161 | 162 | const ident = dep.items[last_slash_i..last_dot_i]; 163 | const path = dep.items[i..last_dot_i]; 164 | 165 | // if there are '/' in proto_file.name after common prefix, it means that 166 | // dep is in a folder above proto_file. if so, we must add a '../' for each 167 | // slash. 168 | const pfname_rest = proto_file.name.items[i..proto_file.name.len]; 169 | const dotdot_count = mem.count(u8, pfname_rest, "/"); 170 | // if the ident is a zig keyword, add a trailing '_' 171 | const is_keyword = std.zig.Token.keywords.get(ident) != null or 172 | types.reserved_words.get(ident) != null; 173 | 174 | return .{ .ident = ident, .path = path, .is_keyword = is_keyword, .dotdot_count = dotdot_count }; 175 | } 176 | 177 | /// writes ident replacing 'delim' with 'delim_replacement' 178 | pub fn writeSplitIdent( 179 | ident: String, 180 | writer: anytype, 181 | mtransform_char_fn: ?*const fn (u8) u8, 182 | delim: []const u8, 183 | delim_replacement: []const u8, 184 | ) !void { 185 | const name = mem.trimLeft(u8, ident.slice(), delim); 186 | var spliter = mem.splitSequence(u8, name, delim); 187 | var i: u16 = 0; 188 | while (spliter.next()) |namepart| : (i += 1) { 189 | if (i != 0) _ = try writer.write(delim_replacement); 190 | if (mtransform_char_fn) |txfn| { 191 | for (namepart) |c| _ = try writer.writeByte(txfn(c)); 192 | } else _ = try writer.write(namepart); 193 | } 194 | } 195 | 196 | pub fn writeFileIdent(ctx: *Context, proto_file: *const FileDescriptorProto, writer: anytype) !void { 197 | try writeSplitIdent(String.init(ctx.gen_path), writer, null, "/", "_"); 198 | _ = try writer.write("_"); 199 | const pname = proto_file.name.slice(); 200 | const last_dot_i = mem.lastIndexOfScalar(u8, pname, '.') orelse pname.len; 201 | try writeSplitIdent(String.init(pname[0..last_dot_i]), writer, null, "/", "_"); 202 | } 203 | 204 | /// recursively write parent.name + delimeter 205 | pub fn writeParentNames( 206 | parent: *const DescriptorProto, 207 | writer: anytype, 208 | ctx: *Context, 209 | delimeter: []const u8, 210 | ) !void { 211 | if (ctx.parents.get(parent)) |pparent| 212 | try writeParentNames(pparent, writer, ctx, delimeter); 213 | 214 | _ = try writer.write(parent.name.slice()); 215 | _ = try writer.write(delimeter); 216 | } 217 | 218 | pub fn printToAll( 219 | ctx: *Context, 220 | comptime fmt: []const u8, 221 | args: anytype, 222 | ) !void { 223 | const writer = ctx.output.writer(ctx.alloc); 224 | _ = try writer.print(fmt, args); 225 | } 226 | pub fn printBanner( 227 | ctx: *Context, 228 | comptime fmt: []const u8, 229 | ) !void { 230 | try printToAll(ctx, 231 | \\ 232 | \\// --- 233 | \\// 234 | ++ fmt ++ 235 | \\ 236 | \\// --- 237 | \\ 238 | \\ 239 | , .{}); 240 | } 241 | 242 | pub fn genFile( 243 | proto_file: *const FileDescriptorProto, 244 | ctx: *Context, 245 | ) !void { 246 | try printBanner(ctx, "prelude"); 247 | try generator.genPrelude(proto_file, ctx); 248 | 249 | try printBanner(ctx, "typedefs"); 250 | for (proto_file.enum_type.slice()) |enum_proto| { 251 | try generator.genEnum(enum_proto, proto_file, ctx); 252 | } 253 | 254 | for (proto_file.message_type.slice()) |desc_proto| 255 | try generator.genMessageTypedef(desc_proto, proto_file, ctx); 256 | 257 | try printBanner(ctx, "message types"); 258 | for (proto_file.message_type.slice()) |desc_proto| { 259 | try generator.genMessage(desc_proto, proto_file, ctx); 260 | } 261 | 262 | try printBanner(ctx, "tests"); 263 | for (proto_file.enum_type.slice()) |enum_proto| { 264 | try generator.genEnumTest(enum_proto, proto_file, ctx); 265 | } 266 | for (proto_file.message_type.slice()) |desc_proto| { 267 | try generator.genMessageTest(desc_proto, proto_file, ctx); 268 | } 269 | try generator.genPostlude(proto_file, ctx); 270 | } 271 | 272 | fn filenameWithExtension(buf: []u8, filename: String, extension: []const u8) ![]const u8 { 273 | const split_filename = common.splitOn([]const u8, filename.slice(), '.'); 274 | return std.fmt.bufPrint(buf, "{s}.{s}", .{ split_filename[0], extension }); 275 | } 276 | 277 | fn createFile(ctx: *Context, file_to_gen: String, extension: []const u8) !std.fs.File { 278 | const filename = 279 | try filenameWithExtension(&ctx.buf, file_to_gen, extension); 280 | const filepath = 281 | try std.fs.path.join(ctx.alloc, &.{ ctx.gen_path, filename }); 282 | defer ctx.alloc.free(filepath); 283 | const dirname = std.fs.path.dirname(filepath) orelse unreachable; 284 | try std.fs.cwd().makePath(dirname); 285 | return std.fs.cwd().createFile(filepath, .{}) catch |e| 286 | return genErr("error creating file '{s}'", .{filepath}, e); 287 | } 288 | 289 | fn populateParents( 290 | ctx: *Context, 291 | node: Node, 292 | mparent: ?*const DescriptorProto, 293 | ) !void { 294 | switch (node) { 295 | .named => unreachable, 296 | .enum_ => |enum_| try ctx.parents.putNoClobber(ctx.alloc, enum_, mparent.?), 297 | .message => |message| { 298 | // don't insert if parent == null (top level) 299 | if (mparent) |parent| 300 | try ctx.parents.putNoClobber(ctx.alloc, message, parent); 301 | for (message.nested_type.slice()) |nested| 302 | try populateParents(ctx, .{ .message = nested }, message); 303 | for (message.enum_type.slice()) |enum_| 304 | try populateParents(ctx, .{ .enum_ = enum_ }, message); 305 | }, 306 | } 307 | } 308 | 309 | pub fn genPopulateMaps(ctx: *Context) !void { 310 | // populate depmap 311 | for (ctx.req.proto_file.slice()) |proto_file| 312 | try ctx.depmap.putNoClobber(ctx.alloc, proto_file.name.slice(), proto_file); 313 | 314 | // populate parents 315 | for (ctx.req.proto_file.slice()) |proto_file| { 316 | // skip top level enums - they can't have children 317 | for (proto_file.message_type.slice()) |message| 318 | try populateParents(ctx, .{ .message = message }, null); 319 | } 320 | } 321 | 322 | pub fn gen(ctx: *Context) !CodeGeneratorResponse { 323 | var res = CodeGeneratorResponse.init(); 324 | try genPopulateMaps(ctx); 325 | res.set(.supported_features, @as(u64, @intCast(@intFromEnum(CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL)))); 326 | 327 | if (output_format == .c) { 328 | log.err("TODO support output_format == .c", .{}); 329 | return error.Todo; 330 | } 331 | 332 | for (ctx.req.file_to_generate.slice()) |file_to_gen| { 333 | const proto_file = ctx.depmap.get(file_to_gen.slice()) orelse 334 | return genErr( 335 | "file_to_gen '{s}' not found in req.proto_file", 336 | .{file_to_gen}, 337 | error.MissingDependency, 338 | ); 339 | ctx.output.items.len = 0; 340 | try genFile(proto_file, ctx); 341 | var file = try ctx.alloc.create(CodeGeneratorResponse.File); 342 | file.* = CodeGeneratorResponse.File.init(); 343 | try ctx.output.append(ctx.alloc, 0); 344 | const tree = try std.zig.Ast.parse(ctx.alloc, ctx.output.items[0 .. ctx.output.items.len - 1 :0], .zig); 345 | const formatted_source = try tree.render(ctx.alloc); 346 | file.set(.content, String.init(formatted_source)); 347 | if (!mem.endsWith(u8, file_to_gen.slice(), ".proto")) return error.NonProtoFile; 348 | const pb_zig_filename = try mem.concat(ctx.alloc, u8, &.{ 349 | file_to_gen.items[0 .. file_to_gen.len - ".proto".len], 350 | ".pb.zig", 351 | }); 352 | file.set(.name, String.init(pb_zig_filename)); 353 | try res.file.append(ctx.alloc, file); 354 | } 355 | return res; 356 | } 357 | -------------------------------------------------------------------------------- /src/google/protobuf/compiler/plugin.pb.zig: -------------------------------------------------------------------------------- 1 | //! 2 | //! this file was originally adapted from https://github.com/protobuf-c/protobuf-c/blob/master/protobuf-c/protobuf-c.h 3 | //! by running `$ zig translate-c` on this file and then doing lots and lots and lots and lots of editing. 4 | //! 5 | //! it is an effort to bootstrap the project and should eventually be generated 6 | //! from https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/descriptor.proto 7 | //! and https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/compiler/plugin.proto 8 | //! 9 | 10 | const std = @import("std"); 11 | const mem = std.mem; 12 | const assert = std.debug.assert; 13 | const pb = @import("protobuf"); 14 | const extern_types = pb.extern_types; 15 | const String = extern_types.String; 16 | const ListMut = extern_types.ListMut; 17 | const types = pb.types; 18 | const ListMutScalar = extern_types.ListMutScalar; 19 | const MessageMixins = types.MessageMixins; 20 | const Message = types.Message; 21 | const FieldDescriptor = types.FieldDescriptor; 22 | const FileDescriptorProto = pb.descriptor.FileDescriptorProto; 23 | 24 | pub const Version = extern struct { 25 | base: Message, 26 | major: i32 = 0, 27 | minor: i32 = 0, 28 | patch: i32 = 0, 29 | suffix: String = String.initEmpty(), 30 | 31 | pub const field_ids = [_]c_uint{ 1, 2, 3, 4 }; 32 | pub const opt_field_ids = [_]c_uint{ 1, 2, 3, 4 }; 33 | pub usingnamespace MessageMixins(@This()); 34 | 35 | pub const field_descriptors = [4]FieldDescriptor{ 36 | FieldDescriptor.init( 37 | "major", 38 | 1, 39 | .LABEL_OPTIONAL, 40 | .TYPE_INT32, 41 | @offsetOf(Version, "major"), 42 | null, 43 | null, 44 | 0, 45 | ), 46 | FieldDescriptor.init( 47 | "minor", 48 | 2, 49 | .LABEL_OPTIONAL, 50 | .TYPE_INT32, 51 | @offsetOf(Version, "minor"), 52 | null, 53 | null, 54 | 0, 55 | ), 56 | FieldDescriptor.init( 57 | "patch", 58 | 3, 59 | .LABEL_OPTIONAL, 60 | .TYPE_INT32, 61 | @offsetOf(Version, "patch"), 62 | null, 63 | null, 64 | 0, 65 | ), 66 | FieldDescriptor.init( 67 | "suffix", 68 | 4, 69 | .LABEL_OPTIONAL, 70 | .TYPE_STRING, 71 | @offsetOf(Version, "suffix"), 72 | null, 73 | null, 74 | 0, 75 | ), 76 | }; 77 | }; 78 | 79 | pub const CodeGeneratorRequest = extern struct { 80 | base: Message, 81 | file_to_generate: ListMutScalar(String) = .{}, 82 | parameter: String = String.initEmpty(), 83 | proto_file: ListMut(*FileDescriptorProto) = .{}, 84 | compiler_version: *Version = undefined, 85 | 86 | comptime { 87 | // @compileLog(@sizeOf(CodeGeneratorRequest)); 88 | assert(@sizeOf(CodeGeneratorRequest) == 112); 89 | // @compileLog(@offsetOf(CodeGeneratorRequest, "proto_file")); 90 | assert(@offsetOf(CodeGeneratorRequest, "proto_file") == 0x50); // == 80 91 | } 92 | 93 | pub const field_ids = [_]c_uint{ 1, 2, 15, 3 }; 94 | pub const opt_field_ids = [_]c_uint{ 2, 3 }; 95 | pub usingnamespace MessageMixins(@This()); 96 | 97 | pub const field_descriptors = [4]FieldDescriptor{ 98 | FieldDescriptor.init( 99 | "file_to_generate", 100 | 1, 101 | .LABEL_REPEATED, 102 | .TYPE_STRING, 103 | @offsetOf(CodeGeneratorRequest, "file_to_generate"), 104 | null, 105 | null, 106 | 0, 107 | ), 108 | FieldDescriptor.init( 109 | "parameter", 110 | 2, 111 | .LABEL_OPTIONAL, 112 | .TYPE_STRING, 113 | @offsetOf(CodeGeneratorRequest, "parameter"), 114 | null, 115 | null, 116 | 0, 117 | ), 118 | FieldDescriptor.init( 119 | "proto_file", 120 | 15, 121 | .LABEL_REPEATED, 122 | .TYPE_MESSAGE, 123 | @offsetOf(CodeGeneratorRequest, "proto_file"), 124 | &FileDescriptorProto.descriptor, 125 | null, 126 | 0, 127 | ), 128 | FieldDescriptor.init( 129 | "compiler_version", 130 | 3, 131 | .LABEL_OPTIONAL, 132 | .TYPE_MESSAGE, 133 | @offsetOf(CodeGeneratorRequest, "compiler_version"), 134 | &Version.descriptor, 135 | null, 136 | 0, 137 | ), 138 | }; 139 | }; 140 | 141 | pub const CodeGeneratorResponse = extern struct { 142 | base: Message, 143 | @"error": String = String.empty, 144 | supported_features: u64 = 0, 145 | file: ListMut(*CodeGeneratorResponse.File) = .{}, 146 | 147 | pub const field_ids = [_]c_uint{ 1, 2, 15 }; 148 | pub const opt_field_ids = [_]c_uint{ 1, 2 }; 149 | pub const is_map_entry = false; 150 | 151 | pub usingnamespace MessageMixins(@This()); 152 | pub const field_descriptors = [_]FieldDescriptor{ 153 | FieldDescriptor.init( 154 | "error", 155 | 1, 156 | .LABEL_OPTIONAL, 157 | .TYPE_STRING, 158 | @offsetOf(CodeGeneratorResponse, "error"), 159 | null, 160 | null, 161 | 0, 162 | ), 163 | FieldDescriptor.init( 164 | "supported_features", 165 | 2, 166 | .LABEL_OPTIONAL, 167 | .TYPE_UINT64, 168 | @offsetOf(CodeGeneratorResponse, "supported_features"), 169 | null, 170 | null, 171 | 0, 172 | ), 173 | FieldDescriptor.init( 174 | "file", 175 | 15, 176 | .LABEL_REPEATED, 177 | .TYPE_MESSAGE, 178 | @offsetOf(CodeGeneratorResponse, "file"), 179 | &CodeGeneratorResponse.File.descriptor, 180 | null, 181 | 0, 182 | ), 183 | }; 184 | 185 | pub const File = extern struct { 186 | base: Message, 187 | name: String = String.empty, 188 | insertion_point: String = String.empty, 189 | content: String = String.empty, 190 | generated_code_info: *pb.descriptor.GeneratedCodeInfo = undefined, 191 | 192 | pub const field_ids = [_]c_uint{ 1, 2, 15, 16 }; 193 | pub const opt_field_ids = [_]c_uint{ 1, 2, 15, 16 }; 194 | pub const is_map_entry = false; 195 | 196 | pub usingnamespace MessageMixins(@This()); 197 | pub const field_descriptors = [_]FieldDescriptor{ 198 | FieldDescriptor.init( 199 | "name", 200 | 1, 201 | .LABEL_OPTIONAL, 202 | .TYPE_STRING, 203 | @offsetOf(CodeGeneratorResponse.File, "name"), 204 | null, 205 | null, 206 | 0, 207 | ), 208 | FieldDescriptor.init( 209 | "insertion_point", 210 | 2, 211 | .LABEL_OPTIONAL, 212 | .TYPE_STRING, 213 | @offsetOf(CodeGeneratorResponse.File, "insertion_point"), 214 | null, 215 | null, 216 | 0, 217 | ), 218 | FieldDescriptor.init( 219 | "content", 220 | 15, 221 | .LABEL_OPTIONAL, 222 | .TYPE_STRING, 223 | @offsetOf(CodeGeneratorResponse.File, "content"), 224 | null, 225 | null, 226 | 0, 227 | ), 228 | FieldDescriptor.init( 229 | "generated_code_info", 230 | 16, 231 | .LABEL_OPTIONAL, 232 | .TYPE_MESSAGE, 233 | @offsetOf(CodeGeneratorResponse.File, "generated_code_info"), 234 | &pb.descriptor.GeneratedCodeInfo.descriptor, 235 | null, 236 | 0, 237 | ), 238 | }; 239 | }; 240 | pub const Feature = enum(i32) { 241 | FEATURE_NONE = 0, 242 | FEATURE_PROTO3_OPTIONAL = 1, 243 | 244 | pub usingnamespace types.EnumMixins(@This()); 245 | }; 246 | }; 247 | -------------------------------------------------------------------------------- /src/lib.zig: -------------------------------------------------------------------------------- 1 | pub const types = @import("types.zig"); 2 | pub const common = @import("common.zig"); 3 | pub const extern_types = @import("extern-types.zig"); 4 | pub const plugin = @import("google/protobuf/compiler/plugin.pb.zig"); 5 | pub const descriptor = @import("google/protobuf/descriptor.pb.zig"); 6 | pub const protobuf = @import("protobuf.zig"); 7 | pub const json = @import("json.zig"); 8 | pub const testing = @import("test-common.zig"); 9 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const Allocator = mem.Allocator; 4 | const pb = @import("protobuf"); 5 | pub const CodeGeneratorRequest = pb.plugin.CodeGeneratorRequest; 6 | pub const gen = @import("gen.zig"); 7 | 8 | /// A simple protoc plugin implementation. Similar to 9 | /// https://github.com/protocolbuffers/protobuf-go/blob/master/cmd/protoc-gen-go/main.go. 10 | /// Reads a CodeGeneratorRequest from stdin and writes a CodeGeneratorResponse to stdout. 11 | pub fn main() !void { 12 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 13 | const alloc = arena.allocator(); 14 | 15 | const input = try std.io.getStdIn().reader().readAllAlloc(alloc, std.math.maxInt(u32)); 16 | 17 | var parse_ctx = pb.protobuf.context(input, alloc); 18 | const message = try parse_ctx.deserialize(&CodeGeneratorRequest.descriptor); 19 | const req = try message.as(CodeGeneratorRequest); 20 | 21 | var gen_ctx = gen.context(alloc, req); 22 | const resp = try gen_ctx.gen(); 23 | const w = std.io.getStdOut().writer(); 24 | try pb.protobuf.serialize(&resp.base, w); 25 | } 26 | -------------------------------------------------------------------------------- /src/meta.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | 4 | pub const Field = union(enum) { 5 | struct_field: std.builtin.Type.StructField, 6 | union_field: std.builtin.Type.UnionField, 7 | 8 | pub fn ty(comptime f: Field) type { 9 | return switch (f) { 10 | .struct_field => |sf| sf.type, 11 | .union_field => |sf| sf.type, 12 | }; 13 | } 14 | pub fn name(comptime f: Field) []const u8 { 15 | return switch (f) { 16 | .struct_field => |sf| sf.name, 17 | .union_field => |sf| sf.name, 18 | }; 19 | } 20 | }; 21 | 22 | /// copy of std.meta.FieldEnum which also includes the field names of any 23 | /// union fields in T. This is non-recursive and only for immediate children. 24 | /// given struct{a: u8, b: union{c, d}} 25 | /// returns enum{a, c, d}; 26 | pub fn FieldEnum(comptime T: type) type { 27 | const EnumField = std.builtin.Type.EnumField; 28 | var fs: []const EnumField = &.{}; 29 | inline for (std.meta.fields(T)) |field| { 30 | const fieldinfo = @typeInfo(field.type); 31 | // if (isStringIn(field.name, exclude_fields)) continue; 32 | switch (fieldinfo) { 33 | .@"union" => inline for (fieldinfo.@"union".fields) |ufield| { 34 | fs = fs ++ [1]EnumField{.{ 35 | .name = field.name ++ "__" ++ ufield.name, 36 | .value = fs.len, 37 | }}; 38 | }, 39 | else => fs = fs ++ [1]EnumField{.{ 40 | .name = field.name, 41 | .value = fs.len, 42 | }}, 43 | } 44 | } 45 | return @Type(.{ .@"enum" = .{ 46 | .tag_type = std.math.IntFittingRange(0, fs.len -| 1), 47 | .fields = fs, 48 | .decls = &.{}, 49 | .is_exhaustive = true, 50 | } }); 51 | } 52 | 53 | pub fn fields(comptime T: type) switch (@typeInfo(T)) { 54 | .@"struct" => []const Field, 55 | else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"), 56 | } { 57 | if (@typeInfo(T) != .@"struct") 58 | @compileError("Expected struct type, found '" ++ @typeName(T) ++ "'"); 59 | 60 | var fs: []const Field = &.{}; 61 | inline for (std.meta.fields(T)) |field| { 62 | const fieldinfo = @typeInfo(field.type); 63 | switch (fieldinfo) { 64 | .@"union" => inline for (fieldinfo.@"union".fields) |ufield| { 65 | var uf = ufield; 66 | uf.name = field.name ++ "__" ++ ufield.name; 67 | fs = fs ++ [1]Field{.{ .union_field = uf }}; 68 | }, 69 | else => fs = fs ++ [1]Field{.{ .struct_field = field }}, 70 | } 71 | } 72 | return fs; 73 | } 74 | 75 | /// copy of std.meta.fieldInfo 76 | pub fn fieldInfo(comptime T: type, comptime field: FieldEnum(T)) switch (@typeInfo(T)) { 77 | .@"struct" => Field, 78 | // .@"union" => std.builtin.Type.UnionField, 79 | // .ErrorSet => std.builtin.Type.Error, 80 | // .@"enum" => std.builtin.Type.EnumField, 81 | else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"), 82 | } { 83 | return fields(T)[@intFromEnum(field)]; 84 | } 85 | 86 | /// copy of std.meta.fieldIndex 87 | pub fn fieldIndex(comptime T: type, comptime name: []const u8) ?comptime_int { 88 | inline for (fields(T), 0..) |field, i| { 89 | if (mem.eql(u8, field.name(), name)) 90 | return i; 91 | } 92 | return null; 93 | } 94 | 95 | /// copy of std.meta.FieldType but adapted to work with union field tagnames of 96 | /// the form `union_field__tagname` 97 | pub fn FieldType(comptime T: type, comptime field: FieldEnum(T)) type { 98 | if (@typeInfo(T) != .@"struct" and @typeInfo(T) != .@"union") { 99 | @compileError("Expected struct or union, found '" ++ @typeName(T) ++ "'"); 100 | } 101 | 102 | return fieldInfo(T, field).ty(); 103 | } 104 | -------------------------------------------------------------------------------- /src/protoc-echo-to-stderr.zig: -------------------------------------------------------------------------------- 1 | //! for capturing output of system installed protoc. 2 | //! used by script/protoc-capture.sh. look here for a usage example. 3 | //! just echoes out whatever protoc sends from stdin to stderr 4 | //! outputs hex representation when $ zig build -Dhex 5 | 6 | const std = @import("std"); 7 | const build_options = @import("build_options"); 8 | const io = std.io; 9 | 10 | pub fn main() !void { 11 | const stdin = io.getStdIn().reader(); 12 | const stderr = io.getStdErr().writer(); 13 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 14 | const alloc = arena.allocator(); 15 | const input = try stdin.readAllAlloc(alloc, std.math.maxInt(u32)); 16 | 17 | // std.debug.print("stdin {}\n", .{input}); 18 | 19 | if (build_options.echo_hex) 20 | try stderr.print("{}", .{std.fmt.fmtSliceHexLower(input)}) 21 | else 22 | _ = try stderr.writeAll(input); 23 | } 24 | -------------------------------------------------------------------------------- /src/test-common.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const testing = std.testing; 4 | const pb = @import("protobuf"); 5 | const types = pb.types; 6 | const Tag = types.Tag; 7 | const protobuf = pb.protobuf; 8 | const String = pb.extern_types.String; 9 | 10 | /// 1. genrate zig-out/bin/protoc-gen-zig by running $ zig build 11 | /// 2. run the following 12 | /// $ protoc --plugin protoc-gen-zig=zig-out/bin/protoc-echo-to-stderr --zig_out=gen `protofile` 13 | pub fn parseWithSystemProtoc(protofile: []const u8, alloc: mem.Allocator) ![]const u8 { 14 | { 15 | const r = try std.process.Child.run(.{ .allocator = alloc, .argv = &.{ "zig", "build" } }); 16 | alloc.free(r.stderr); 17 | alloc.free(r.stdout); 18 | } 19 | 20 | const r = try std.process.Child.run(.{ 21 | .allocator = alloc, 22 | .argv = &.{ 23 | "zig-out/bin/protoc", 24 | "--plugin", 25 | "protoc-gen-zig=zig-out/bin/protoc-echo-to-stderr", 26 | "--zig_out=gen", 27 | protofile, 28 | }, 29 | }); 30 | alloc.free(r.stdout); 31 | return r.stderr; 32 | } 33 | 34 | pub fn deserializeHelper(comptime T: type, protofile: []const u8, alloc: mem.Allocator) !*T { 35 | const bytes = try parseWithSystemProtoc(protofile, alloc); 36 | defer alloc.free(bytes); 37 | return deserializeBytesHelper(T, bytes, alloc); 38 | } 39 | pub fn deserializeBytesHelper(comptime T: type, bytes: []const u8, alloc: mem.Allocator) !*T { 40 | var ctx = protobuf.context(bytes, alloc); 41 | const message = try ctx.deserialize(&T.descriptor); 42 | return try message.as(T); 43 | } 44 | pub fn deserializeHexBytesHelper(comptime T: type, hexbytes: []const u8, alloc: mem.Allocator) !*T { 45 | const out = try alloc.alloc(u8, hexbytes.len / 2); 46 | defer alloc.free(out); 47 | const bytes = try std.fmt.hexToBytes(out, hexbytes); 48 | return deserializeBytesHelper(T, bytes, alloc); 49 | } 50 | 51 | pub fn encodeVarint(comptime T: type, i: T) []const u8 { 52 | var buf: [32]u8 = undefined; // handles upto u512 53 | var fbs = std.io.fixedBufferStream(&buf); 54 | protobuf.writeVarint128(T, i, fbs.writer(), .int) catch unreachable; 55 | return fbs.getWritten(); 56 | } 57 | 58 | pub fn encodeInt(comptime T: type, i: T) []const u8 { 59 | var buf: [32]u8 = undefined; // handles upto u512 60 | var fbs = std.io.fixedBufferStream(&buf); 61 | fbs.writer().writeInt(T, i, .little) catch unreachable; 62 | return fbs.getWritten(); 63 | } 64 | 65 | pub fn encodeFloat(comptime T: type, i: T) []const u8 { 66 | const U = std.meta.Int(.unsigned, @typeInfo(T).float.bits); 67 | return encodeInt(U, @as(U, @bitCast(i))); 68 | } 69 | 70 | pub fn encodeMessage(comptime parts: anytype) []const u8 { 71 | var result: []const u8 = &.{}; 72 | comptime for (std.meta.fields(@TypeOf(parts))) |f| { 73 | switch (f.type) { 74 | Tag => result = result ++ 75 | encodeVarint(usize, @field(parts, f.name).encode()), 76 | comptime_int => result = result ++ 77 | encodeVarint(usize, @field(parts, f.name)), 78 | bool => result = result ++ 79 | encodeVarint(u8, @intFromBool(@field(parts, f.name))), 80 | else => if (isZigString(f.type)) { 81 | result = result ++ @field(parts, f.name); 82 | } else if (isIntegral(f.type)) { 83 | result = result ++ 84 | encodeVarint(f.type, @field(parts, f.name)); 85 | } else @compileError("unsupported type '" ++ @typeName(f.type) ++ "'"), 86 | } 87 | }; 88 | 89 | return result; 90 | } 91 | 92 | pub fn isZigString(comptime T: type) bool { 93 | return comptime blk: { 94 | // Only pointer types can be strings, no optionals 95 | const info = @typeInfo(T); 96 | if (info != .pointer) break :blk false; 97 | const ptr = &info.pointer; 98 | // Check for CV qualifiers that would prevent coerction to []const u8 99 | if (ptr.is_volatile or ptr.is_allowzero) break :blk false; 100 | // If it's already a slice, simple check. 101 | if (ptr.size == .slice) { 102 | break :blk ptr.child == u8; 103 | } 104 | // Otherwise check if it's an array type that coerces to slice. 105 | if (ptr.size == .one) { 106 | const child = @typeInfo(ptr.child); 107 | if (child == .array) { 108 | const arr = &child.array; 109 | break :blk arr.child == u8; 110 | } 111 | } 112 | break :blk false; 113 | }; 114 | } 115 | 116 | pub fn isIntegral(comptime T: type) bool { 117 | return switch (@typeInfo(T)) { 118 | .int, .comptime_int => true, 119 | else => false, 120 | }; 121 | } 122 | 123 | pub fn lengthEncode(comptime parts: anytype) []const u8 { 124 | const m = encodeMessage(parts); 125 | return encodeVarint(usize, m.len) ++ m; 126 | } 127 | 128 | pub const TestError = error{ 129 | TestExpectedEqual, 130 | TestExpectedApproxEqAbs, 131 | TestUnexpectedResult, 132 | }; 133 | 134 | pub fn expectEqual(comptime T: type, data: T, data2: T) TestError!void { 135 | @setEvalBranchQuota(200000); 136 | switch (@typeInfo(T)) { 137 | .int, .bool, .@"enum" => try std.testing.expectEqual(data, data2), 138 | .float => try std.testing.expectApproxEqAbs( 139 | data, 140 | data2, 141 | std.math.floatEps(T), 142 | ), 143 | .@"struct" => if (T == String) { 144 | try std.testing.expectEqualStrings(data.slice(), data2.slice()); 145 | } else if (comptime mem.indexOf( 146 | u8, 147 | @typeName(T), 148 | "extern-types.ArrayList", 149 | ) != null) { 150 | try std.testing.expectEqual(data.len, data2.len); 151 | for (data.slice(), 0..) |it, i| 152 | try expectEqual(@TypeOf(it), it, data2.items[i]); 153 | } else { 154 | const fe = types.FieldEnum(T); 155 | inline for (comptime std.meta.tags(fe)) |tag| { 156 | if (comptime mem.eql(u8, @tagName(tag), "base")) continue; 157 | const F = comptime types.FieldType(T, tag); 158 | if (!@hasDecl(T, "has")) { 159 | const field = @field(data, @tagName(tag)); 160 | const field2 = @field(data2, @tagName(tag)); 161 | try expectEqual(F, field, field2); 162 | } else if (data.has(tag)) { 163 | const finfo = @typeInfo(F); 164 | const field = types.getFieldHelp(T, data, tag); 165 | const field2 = types.getFieldHelp(T, data2, tag); 166 | if (finfo == .@"union") { // oneof fields 167 | const ffe = comptime types.FieldEnum(F); 168 | const ftags = comptime std.meta.tags(ffe); 169 | inline for (T.oneof_field_ids) |oneof_ids| { 170 | inline for (comptime oneof_ids.slice(), 0..) |oneof_id, i| { 171 | const ftag = ftags[i]; 172 | try testing.expect(data.base.hasFieldId(oneof_id) == 173 | data2.base.hasFieldId(oneof_id)); 174 | if (data.base.hasFieldId(oneof_id)) { 175 | const payload = @field(field, @tagName(ftag)); 176 | const payload2 = @field(field2, @tagName(ftag)); 177 | const U = types.FieldType(F, ftag); 178 | try expectEqual(U, payload, payload2); 179 | } 180 | } 181 | } 182 | } else try expectEqual(F, field, field2); 183 | } 184 | } 185 | }, 186 | .pointer => |ptr| switch (ptr.size) { 187 | .one => return expectEqual(ptr.child, data.*, data2.*), 188 | else => @compileError("unsupported type '" ++ @typeName(T) ++ "'"), 189 | }, 190 | else => @compileError("unsupported type '" ++ @typeName(T) ++ "'"), 191 | } 192 | } 193 | 194 | /// recursively initializes a protobuf type, setting each field to a 195 | /// representation of its field_id 196 | pub fn testInit( 197 | comptime T: type, 198 | comptime field_id: ?c_uint, 199 | alloc: mem.Allocator, 200 | ) mem.Allocator.Error!T { 201 | @setEvalBranchQuota(10_000); 202 | switch (@typeInfo(T)) { 203 | .int => return @as(T, @intCast(field_id.?)), 204 | .bool => return true, 205 | .@"enum" => return std.meta.tags(T)[0], 206 | .float => return @as(T, @floatFromInt(field_id.?)), 207 | .@"struct" => if (T == String) { 208 | return String.init(try std.fmt.allocPrint(alloc, "{}", .{field_id.?})); 209 | } else if (comptime mem.indexOf( 210 | u8, 211 | @typeName(T), 212 | "extern-types.ArrayList", 213 | ) != null) { 214 | const child = try testInit(T.Child, field_id, alloc); 215 | const items = try alloc.alloc(T.Child, 1); 216 | items[0] = child; 217 | return pb.extern_types.ArrayListMut(T.Child).init(items); 218 | } else { 219 | var t = T.init(); 220 | const fields = types.fields(T); 221 | const fe = types.FieldEnum(T); 222 | const tags = comptime std.meta.tags(fe); 223 | comptime var i: usize = 1; 224 | inline while (i < fields.len) : (i += 1) { 225 | const field = fields[i]; 226 | const F = field.ty(); 227 | const tag = tags[i]; 228 | if (field == .union_field) { 229 | const payload = try testInit(F, T.field_ids[0], alloc); 230 | t.set(tag, payload); 231 | // skip remaining union fields in this group 232 | if (i < T.field_ids.len) { 233 | const fid = T.field_ids[i]; 234 | const oneof_ids = comptime for (T.oneof_field_ids) |oneof_ids| { 235 | if (mem.indexOfScalar(c_uint, oneof_ids.slice(), fid) != null) 236 | break oneof_ids; 237 | } else unreachable; 238 | i += oneof_ids.len; 239 | } 240 | } else { 241 | t.set(tag, try testInit(F, T.field_ids[i - 1], alloc)); 242 | } 243 | } 244 | return t; 245 | }, 246 | .pointer => |ptr| switch (ptr.size) { 247 | .one => { 248 | const t = try alloc.create(ptr.child); 249 | t.* = try testInit(ptr.child, field_id, alloc); 250 | return t; 251 | }, 252 | else => @compileError("unsupported type '" ++ @typeName(T) ++ "'"), 253 | }, 254 | .@"union" => { 255 | unreachable; 256 | }, 257 | else => @compileError("unsupported type '" ++ @typeName(T) ++ "'"), 258 | } 259 | unreachable; 260 | } 261 | -------------------------------------------------------------------------------- /src/test-conformance.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pb = @import("protobuf"); 3 | const generated = @import("generated"); 4 | const test3 = generated.test_messages_proto3; 5 | const testing = std.testing; 6 | 7 | const talloc = testing.allocator; 8 | test "conf Required.Proto3.ProtobufInput.PrematureEofInPackedFieldValue.INT64" { 9 | const input = "82020180"; 10 | try testing.expectError(error.FieldMissing, pb.testing.deserializeHexBytesHelper( 11 | test3.TestAllTypesProto3, 12 | input, 13 | talloc, 14 | )); 15 | } 16 | 17 | test "conf Required.Proto3.ProtobufInput.IllegalZeroFieldNum_Case_0" { 18 | const input = "\x01DEADBEEF"; 19 | try testing.expectError(error.FieldMissing, pb.testing.deserializeBytesHelper( 20 | test3.TestAllTypesProto3, 21 | input, 22 | talloc, 23 | )); 24 | } 25 | 26 | test "conf Required.Proto3.ProtobufInput.ValidDataScalar.BOOL[4].ProtobufOutput" { 27 | const input = "688080808020"; 28 | const m = try pb.testing.deserializeHexBytesHelper( 29 | test3.TestAllTypesProto3, 30 | input, 31 | talloc, 32 | ); 33 | defer m.base.deinit(talloc); 34 | try testing.expect(m.has(.optional_bool)); 35 | try testing.expect(m.optional_bool); 36 | var buf = std.ArrayList(u8).init(talloc); 37 | defer buf.deinit(); 38 | try pb.protobuf.serialize(&m.base, buf.writer()); 39 | const m2 = try pb.testing.deserializeBytesHelper( 40 | test3.TestAllTypesProto3, 41 | buf.items, 42 | talloc, 43 | ); 44 | defer m2.base.deinit(talloc); 45 | try testing.expect(m2.has(.optional_bool)); 46 | try testing.expect(m2.optional_bool); 47 | } 48 | 49 | test "conf Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.BOOL.ProtobufOutput" { 50 | const input = "6800680168ffffffffffffffffff0168cec2f10568808080802068ffffffffffffffff7f6880808080808080808001"; 51 | const m = try pb.testing.deserializeHexBytesHelper( 52 | test3.TestAllTypesProto3, 53 | input, 54 | talloc, 55 | ); 56 | defer m.base.deinit(talloc); 57 | try testing.expect(m.has(.optional_bool)); 58 | try testing.expect(m.optional_bool); 59 | var buf = std.ArrayList(u8).init(talloc); 60 | defer buf.deinit(); 61 | try pb.protobuf.serialize(&m.base, buf.writer()); 62 | const m2 = try pb.testing.deserializeBytesHelper( 63 | test3.TestAllTypesProto3, 64 | buf.items, 65 | talloc, 66 | ); 67 | defer m2.base.deinit(talloc); 68 | try testing.expect(m2.has(.optional_bool)); 69 | try testing.expect(m2.optional_bool); 70 | } 71 | 72 | test "conf Required.Proto3.ProtobufInput.ValidDataRepeated.BOOL.PackedInput.ProtobufOutput" { 73 | const input = "da02280001ffffffffffffffffff01cec2f1058080808020ffffffffffffffff7f80808080808080808001"; 74 | const m = try pb.testing.deserializeHexBytesHelper( 75 | test3.TestAllTypesProto3, 76 | input, 77 | talloc, 78 | ); 79 | defer m.base.deinit(talloc); 80 | try testing.expect(m.has(.repeated_bool)); 81 | try testing.expectEqual(@as(usize, 7), m.repeated_bool.len); 82 | try testing.expectEqual(false, m.repeated_bool.items[0]); 83 | try testing.expectEqual(false, m.repeated_bool.items[1]); 84 | try testing.expectEqual(false, m.repeated_bool.items[2]); 85 | try testing.expectEqual(false, m.repeated_bool.items[3]); 86 | try testing.expectEqual(true, m.repeated_bool.items[4]); 87 | try testing.expectEqual(false, m.repeated_bool.items[5]); 88 | try testing.expectEqual(false, m.repeated_bool.items[6]); 89 | } 90 | 91 | test "conf Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.SINT32.ProtobufOutput" { 92 | const input = "280028f2c00128feffffff0f28ffffffff0f288280808010"; 93 | const m = try pb.testing.deserializeHexBytesHelper( 94 | test3.TestAllTypesProto3, 95 | input, 96 | talloc, 97 | ); 98 | defer m.base.deinit(talloc); 99 | try testing.expect(m.has(.optional_sint32)); 100 | try testing.expectEqual(@as(i32, 1), m.optional_sint32); 101 | } 102 | 103 | test "conf Required.Proto3.ProtobufInput.ValidDataRepeated.SINT32.UnpackedInput.ProtobufOutput" { 104 | const input = "9802009802f2c0019802feffffff0f9802ffffffff0f98028280808010"; 105 | const m = try pb.testing.deserializeHexBytesHelper( 106 | test3.TestAllTypesProto3, 107 | input, 108 | talloc, 109 | ); 110 | defer m.base.deinit(talloc); 111 | try testing.expect(m.has(.repeated_sint32)); 112 | try testing.expectEqual(@as(usize, 5), m.repeated_sint32.len); 113 | try testing.expectEqual(@as(i32, 0), m.repeated_sint32.items[0]); 114 | try testing.expectEqual(@as(i32, 12345), m.repeated_sint32.items[1]); 115 | try testing.expectEqual(@as(i32, 2147483647), m.repeated_sint32.items[2]); 116 | try testing.expectEqual(@as(i32, -2147483648), m.repeated_sint32.items[3]); 117 | try testing.expectEqual(@as(i32, 1), m.repeated_sint32.items[4]); 118 | } 119 | 120 | test "conf Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput" { 121 | // this is failing because nested messages aren't working yet 122 | // TODO re-enable after #1 is resolved and recursive messages work 123 | if (true) return error.SkipZigTest; 124 | const input = "820709120708011001c8050182070712051001c80501"; 125 | testing.log_level = .debug; 126 | const m = try pb.testing.deserializeHexBytesHelper( 127 | test3.TestAllTypesProto3, 128 | input, 129 | talloc, 130 | ); 131 | defer m.base.deinit(talloc); 132 | try testing.expect(m.has(.oneof_field__oneof_nested_message)); 133 | // TODO add expectations 134 | const nested = m.oneof_field.oneof_nested_message; 135 | _ = nested; 136 | // try testing.expect(nested.has(.corecursive)); 137 | } 138 | 139 | test "conf Required.Proto3.ProtobufInput.ValidDataMap.STRING.MESSAGE.MergeValue.ProtobufOutput" { 140 | // this is failing because nested messages aren't working yet 141 | // TODO re-enable after #1 is resolved and recursive messages work 142 | if (true) return error.SkipZigTest; 143 | const input = "ba040b0a00120712050801f80101ba040b0a00120712051001f80101"; 144 | testing.log_level = .debug; 145 | const m = try pb.testing.deserializeHexBytesHelper( 146 | test3.TestAllTypesProto3, 147 | input, 148 | talloc, 149 | ); 150 | defer m.base.deinit(talloc); 151 | // TODO add expectations 152 | const nested = m.oneof_field.oneof_nested_message; 153 | _ = nested; 154 | // try testing.expect(nested.has(.corecursive)); 155 | } 156 | 157 | test "conf Required.Proto3.ProtobufInput.UnknownVarint.ProtobufOutput" { 158 | const input = "a81f01"; 159 | const m = try pb.testing.deserializeHexBytesHelper( 160 | test3.TestAllTypesProto3, 161 | input, 162 | talloc, 163 | ); 164 | defer m.base.deinit(talloc); 165 | var buf = std.ArrayList(u8).init(talloc); 166 | defer buf.deinit(); 167 | try pb.protobuf.serialize(&m.base, buf.writer()); 168 | const hex = try std.fmt.allocPrint( 169 | talloc, 170 | "{}", 171 | .{std.fmt.fmtSliceHexLower(buf.items)}, 172 | ); 173 | defer talloc.free(hex); 174 | try testing.expectEqualSlices(u8, input, hex); 175 | } 176 | 177 | test "conf Required.Proto3.ProtobufInput.ValidDataMap.STRING.ENUM.Unordered.JsonOutput" { 178 | const input = "ca040510010a0161"; 179 | const m = try pb.testing.deserializeHexBytesHelper( 180 | test3.TestAllTypesProto3, 181 | input, 182 | talloc, 183 | ); 184 | defer m.base.deinit(talloc); 185 | var buf = std.ArrayList(u8).init(talloc); 186 | defer buf.deinit(); 187 | try pb.json.serialize(&m.base, buf.writer(), .{}); 188 | const expected = 189 | \\{"map_string_nested_enum":{"a":"BAR"}} 190 | ; 191 | try testing.expectEqualSlices(u8, expected, buf.items); 192 | } 193 | 194 | test "conf Required.Proto3.ProtobufInput.ValidDataMap.INT32.INT32.DuplicateKey.JsonOutput" { 195 | const input = "c2030408011000c2030408011001"; 196 | const m = try pb.testing.deserializeHexBytesHelper( 197 | test3.TestAllTypesProto3, 198 | input, 199 | talloc, 200 | ); 201 | defer m.base.deinit(talloc); 202 | var buf = std.ArrayList(u8).init(talloc); 203 | defer buf.deinit(); 204 | try pb.json.serialize(&m.base, buf.writer(), .{}); 205 | const expected = 206 | \\{"map_int32_int32":{"1":1}} 207 | ; 208 | try testing.expectEqualSlices(u8, expected, buf.items); 209 | } 210 | -------------------------------------------------------------------------------- /src/test-deserialize.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const testing = std.testing; 4 | 5 | const pb = @import("protobuf"); 6 | const pbtypes = pb.types; 7 | const plugin = pb.plugin; 8 | const descr = pb.descriptor; 9 | const protobuf = pb.protobuf; 10 | const ptrfmt = pb.common.ptrfmt; 11 | const CodeGeneratorRequest = plugin.CodeGeneratorRequest; 12 | const FieldDescriptorProto = descr.FieldDescriptorProto; 13 | const Tag = pbtypes.Tag; 14 | const tcommon = pb.testing; 15 | const lengthEncode = tcommon.lengthEncode; 16 | const encodeMessage = tcommon.encodeMessage; 17 | const encodeVarint = tcommon.encodeVarint; 18 | const deserializeHelper = tcommon.deserializeHelper; 19 | const deserializeBytesHelper = tcommon.deserializeBytesHelper; 20 | const deserializeHexBytesHelper = tcommon.deserializeHexBytesHelper; 21 | 22 | const talloc = testing.allocator; 23 | // var tarena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 24 | // const talloc = tarena.allocator(); 25 | 26 | test "examples/only_enum - system protoc" { 27 | const req = try deserializeHelper(CodeGeneratorRequest, "examples/only_enum.proto", talloc); 28 | defer req.base.deinit(talloc); 29 | try testing.expectEqual(1, req.file_to_generate.len); 30 | try testing.expectEqual(1, req.file_to_generate.cap); 31 | try testing.expectEqualStrings("examples/only_enum.proto", req.file_to_generate.items[0].slice()); 32 | try testing.expectEqual(1, req.proto_file.len); 33 | const pf = req.proto_file; 34 | try testing.expectEqual(1, pf.len); 35 | const pf0et = pf.items[0].enum_type; 36 | try testing.expectEqual(1, pf0et.len); 37 | const pf0et0val = pf0et.items[0].value; 38 | try testing.expectEqual(4, pf0et0val.len); 39 | try testing.expectEqualStrings("NONE", pf0et0val.items[0].name.slice()); 40 | try testing.expectEqual(0, pf0et0val.items[0].number); 41 | try testing.expectEqualStrings("A", pf0et0val.items[1].name.slice()); 42 | try testing.expectEqual(1, pf0et0val.items[1].number); 43 | try testing.expectEqualStrings("B", pf0et0val.items[2].name.slice()); 44 | try testing.expectEqual(2, pf0et0val.items[2].number); 45 | try testing.expectEqualStrings("C", pf0et0val.items[3].name.slice()); 46 | try testing.expectEqual(3, pf0et0val.items[3].number); 47 | const pfscloc = pf.items[0].source_code_info.location; 48 | try testing.expectEqual(16, pfscloc.len); 49 | } 50 | 51 | test "examples/only_enum-1 - no deps" { 52 | // `input` was obtained by running $ zig build -Dhex && script/protoc-capture.sh examples/only_enum-1.proto 53 | const input = "0a1a6578616d706c65732f6f6e6c795f656e756d2d312e70726f746f1a080803100c180422007a8f010a1a6578616d706c65732f6f6e6c795f656e756d2d312e70726f746f2a140a08536f6d654b696e6412080a044e4f4e4510004a530a061204000004010a080a010c12030000120a0a0a0205001204020004010a0a0a03050001120302050d0a0b0a0405000200120303040d0a0c0a05050002000112030304080a0c0a0505000200021203030b0c620670726f746f33"; 54 | const req = try deserializeHexBytesHelper(CodeGeneratorRequest, input, talloc); 55 | defer req.base.deinit(talloc); 56 | try testing.expectEqual(1, req.file_to_generate.len); 57 | try testing.expectEqual(1, req.file_to_generate.cap); 58 | try testing.expectEqualStrings("examples/only_enum-1.proto", req.file_to_generate.items[0].slice()); 59 | try testing.expectEqual(1, req.proto_file.len); 60 | const pf = req.proto_file; 61 | try testing.expectEqual(1, pf.len); 62 | const pf0et = pf.items[0].enum_type; 63 | try testing.expectEqual(1, pf0et.len); 64 | const pf0et0val = pf0et.items[0].value; 65 | try testing.expectEqual(1, pf0et0val.len); 66 | try testing.expectEqualStrings("NONE", pf0et0val.items[0].name.slice()); 67 | try testing.expectEqual(0, pf0et0val.items[0].number); 68 | const pfscloc = pf.items[0].source_code_info.location; 69 | try testing.expectEqual(7, pfscloc.len); 70 | try testing.expectEqual(0, pfscloc.items[0].path.len); 71 | try testing.expectEqual(4, pfscloc.items[0].span.len); 72 | try testing.expectEqual(1, pfscloc.items[1].path.len); 73 | try testing.expectEqual(3, pfscloc.items[1].span.len); 74 | try testing.expectEqual(2, pfscloc.items[2].path.len); 75 | try testing.expectEqual(4, pfscloc.items[2].span.len); 76 | try testing.expectEqual(3, pfscloc.items[3].path.len); 77 | try testing.expectEqual(3, pfscloc.items[3].span.len); 78 | try testing.expectEqual(4, pfscloc.items[4].path.len); 79 | try testing.expectEqual(3, pfscloc.items[4].span.len); 80 | try testing.expectEqual(5, pfscloc.items[5].path.len); 81 | try testing.expectEqual(3, pfscloc.items[5].span.len); 82 | try testing.expectEqual(5, pfscloc.items[6].path.len); 83 | try testing.expectEqual(3, pfscloc.items[6].span.len); 84 | } 85 | 86 | test "nested lists" { 87 | const message = comptime encodeMessage(.{ 88 | Tag.init(.LEN, 15), // CodeGeneratorRequest.proto_file 89 | lengthEncode(.{ 90 | Tag.init(.LEN, 5), // FileDescriptorProto.enum_type 91 | lengthEncode(.{ 92 | Tag.init(.LEN, 2), // EnumDescriptorProto.value 93 | lengthEncode(.{ 94 | Tag.init(.LEN, 1), // EnumValueDescriptorProto.name 95 | lengthEncode(.{"field0"}), 96 | Tag.init(.VARINT, 2), // EnumValueDescriptorProto.number 97 | 1, 98 | }), 99 | }), 100 | }), 101 | }); 102 | const req = try deserializeBytesHelper(CodeGeneratorRequest, message, talloc); 103 | defer req.base.deinit(talloc); 104 | try testing.expectEqual(1, req.proto_file.len); 105 | try testing.expectEqual(1, req.proto_file.items[0].enum_type.len); 106 | try testing.expectEqual(1, req.proto_file.items[0].enum_type.items[0].value.len); 107 | try testing.expectEqualStrings("field0", req.proto_file.items[0].enum_type.items[0].value.items[0].name.slice()); 108 | try testing.expectEqual(1, req.proto_file.items[0].enum_type.items[0].value.items[0].number); 109 | } 110 | 111 | test "examples/only_message - no deps" { 112 | // testing.log_level = .info; 113 | // `input` was obtained by running $ zig build -Dhex && script/protoc-capture.sh -I examples/ examples/only_message.proto 114 | const input = "0a126f6e6c795f6d6573736167652e70726f746f1a080803100c180422007a95020a0f6f6e6c795f656e756d2e70726f746f2a290a08536f6d654b696e6412080a044e4f4e45100012050a0141100112050a0142100212050a014310034ace010a061204000007010a080a010c12030000120a0a0a0205001204020007010a0a0a03050001120302050d0a0b0a0405000200120303040d0a0c0a05050002000112030304080a0c0a0505000200021203030b0c0a0b0a0405000201120304040a0a0c0a05050002010112030404050a0c0a05050002010212030408090a0b0a0405000202120305040a0a0c0a05050002020112030504050a0c0a05050002020212030508090a0b0a0405000203120306040a0a0c0a05050002030112030604050a0c0a0505000203021203060809620670726f746f337ac9030a126f6e6c795f6d6573736167652e70726f746f1a0f6f6e6c795f656e756d2e70726f746f22610a06506572736f6e12120a046e616d6518012001280952046e616d65120e0a0269641802200128055202696412140a05656d61696c1803200128095205656d61696c121d0a046b696e6418042001280e32092e536f6d654b696e6452046b696e644ab6020a06120400000a010a080a010c12030000120a090a02030012030300190a0a0a020400120405000a010a0a0a03040001120305080e0a0b0a040400020012030602120a0c0a05040002000512030602080a0c0a050400020001120306090d0a0c0a05040002000312030610110a300a0404000201120307020f222320556e69717565204944206e756d62657220666f72207468697320706572736f6e2e0a0a0c0a05040002010512030702070a0c0a050400020101120307080a0a0c0a0504000201031203070d0e0a0b0a040400020212030802130a0c0a05040002020512030802080a0c0a050400020201120308090e0a0c0a05040002020312030811120a0b0a040400020312030902140a0c0a050400020306120309020a0a0c0a0504000203011203090b0f0a0c0a0504000203031203091213620670726f746f33"; 115 | const req = try deserializeHexBytesHelper(CodeGeneratorRequest, input, talloc); 116 | defer req.base.deinit(talloc); 117 | try testing.expectEqual(1, req.file_to_generate.len); 118 | try testing.expectEqualStrings("only_message.proto", req.file_to_generate.items[0].slice()); 119 | const pf = req.proto_file; 120 | try testing.expectEqual(2, pf.len); 121 | try testing.expectEqual(16, pf.items[0].source_code_info.location.len); 122 | try testing.expectEqual(21, pf.items[1].source_code_info.location.len); 123 | const pf0et = pf.items[0].enum_type; 124 | try testing.expectEqual(1, pf0et.len); 125 | try testing.expectEqualStrings("SomeKind", pf0et.items[0].name.slice()); 126 | try testing.expectEqual(4, pf0et.items[0].value.len); 127 | try testing.expectEqualStrings("only_message.proto", pf.items[1].name.slice()); 128 | try testing.expectEqual(1, pf.items[1].dependency.len); 129 | try testing.expectEqualStrings("only_enum.proto", pf.items[1].dependency.items[0].slice()); 130 | 131 | const pf1mt = pf.items[1].message_type; 132 | try testing.expectEqual(1, pf1mt.len); 133 | const mt0 = pf1mt.items[0]; 134 | try testing.expectEqualStrings("Person", mt0.name.slice()); 135 | try testing.expectEqual(4, mt0.field.len); 136 | 137 | try testing.expectEqualStrings("name", mt0.field.items[0].name.slice()); 138 | try testing.expectEqualStrings("name", mt0.field.items[0].json_name.slice()); 139 | try testing.expectEqual(1, mt0.field.items[0].number); 140 | try testing.expectEqual(FieldDescriptorProto.Label.LABEL_OPTIONAL, mt0.field.items[0].label); 141 | try testing.expectEqual(FieldDescriptorProto.Type.TYPE_STRING, mt0.field.items[0].type); 142 | 143 | try testing.expectEqualStrings("id", mt0.field.items[1].name.slice()); 144 | try testing.expectEqualStrings("id", mt0.field.items[1].json_name.slice()); 145 | try testing.expectEqual(2, mt0.field.items[1].number); 146 | try testing.expectEqual(FieldDescriptorProto.Label.LABEL_OPTIONAL, mt0.field.items[1].label); 147 | try testing.expectEqual(FieldDescriptorProto.Type.TYPE_INT32, mt0.field.items[1].type); 148 | 149 | try testing.expectEqualStrings("email", mt0.field.items[2].name.slice()); 150 | try testing.expectEqualStrings("email", mt0.field.items[2].json_name.slice()); 151 | try testing.expectEqual(3, mt0.field.items[2].number); 152 | try testing.expectEqual(FieldDescriptorProto.Label.LABEL_OPTIONAL, mt0.field.items[2].label); 153 | try testing.expectEqual(FieldDescriptorProto.Type.TYPE_STRING, mt0.field.items[2].type); 154 | 155 | try testing.expectEqualStrings("kind", mt0.field.items[3].name.slice()); 156 | try testing.expectEqualStrings("kind", mt0.field.items[3].json_name.slice()); 157 | try testing.expectEqual(4, mt0.field.items[3].number); 158 | try testing.expectEqual(FieldDescriptorProto.Label.LABEL_OPTIONAL, mt0.field.items[3].label); 159 | try testing.expectEqual(FieldDescriptorProto.Type.TYPE_ENUM, mt0.field.items[3].type); 160 | } 161 | 162 | test "examples/all_types - system protoc" { 163 | const req = try deserializeHelper(CodeGeneratorRequest, "examples/all_types.proto", talloc); 164 | defer req.base.deinit(talloc); 165 | try testing.expectEqual(1, req.file_to_generate.len); 166 | try testing.expectEqualStrings("examples/all_types.proto", req.file_to_generate.items[0].slice()); 167 | } 168 | 169 | test "message deinit" { 170 | const message = comptime encodeMessage(.{ 171 | Tag.init(.LEN, 15), // CodeGeneratorRequest.proto_file 172 | lengthEncode(.{ 173 | Tag.init(.LEN, 5), // FileDescriptorProto.enum_type 174 | lengthEncode(.{ 175 | Tag.init(.LEN, 2), // EnumDescriptorProto.value 176 | lengthEncode(.{ 177 | Tag.init(.LEN, 1), // EnumValueDescriptorProto.name 178 | lengthEncode(.{"field0"}), 179 | Tag.init(.VARINT, 2), // EnumValueDescriptorProto.number 180 | 1, 181 | }), 182 | }), 183 | Tag.init(.LEN, 12), // FileDescriptorProto.syntax 184 | lengthEncode(.{"proto3"}), 185 | }), 186 | }); 187 | const req = try deserializeBytesHelper(CodeGeneratorRequest, message, testing.allocator); 188 | defer req.base.deinit(testing.allocator); 189 | } 190 | 191 | test "message missing required fields" { 192 | testing.log_level = .err; 193 | const req = deserializeBytesHelper(descr.UninterpretedOption.NamePart, "", talloc); 194 | try testing.expectError(error.RequiredFieldMissing, req); 195 | } 196 | 197 | test "message with map fields / nested types" { 198 | // this test also exercises nested types 199 | const req = try deserializeHelper(CodeGeneratorRequest, "examples/map.proto", talloc); 200 | defer req.base.deinit(talloc); 201 | try testing.expectEqual(1, req.base.unknown_fields.len); 202 | try testing.expectEqual(1, req.proto_file.len); 203 | try testing.expectEqual(1, req.proto_file.items[0].message_type.len); 204 | try testing.expectEqual(1, req.proto_file.items[0].message_type.items[0].nested_type.len); 205 | } 206 | 207 | test "free oneof field when overwritten" { 208 | const oneof_2 = @import("generated").oneof_2; 209 | const T = oneof_2.TestAllTypesProto3; 210 | 211 | const message = comptime encodeMessage(.{ 212 | Tag.init(.LEN, 113), // TestAllTypesProto3.oneof_field__oneof_string 213 | lengthEncode(.{"oneof_field__oneof_string"}), 214 | Tag.init(.LEN, 114), // TestAllTypesProto3.oneof_field__oneof_bytes 215 | lengthEncode(.{"oneof_field__oneof_bytes"}), 216 | Tag.init(.LEN, 112), // TestAllTypesProto3.oneof_field__oneof_nested_message 217 | lengthEncode(.{ 218 | Tag.init(.VARINT, 1), // TestAllTypesProto3.NestedMessage.a 219 | 42, 220 | }), 221 | Tag.init(.VARINT, 111), // TestAllTypesProto3.oneof_field__oneof_uint32 222 | 42, 223 | }); 224 | 225 | var ctx = protobuf.context(message, talloc); 226 | const m = try ctx.deserialize(&T.descriptor); 227 | defer m.deinit(talloc); 228 | } 229 | 230 | test "deser group" { 231 | const group = @import("generated").group; 232 | const T = group.Grouped; 233 | 234 | const message = comptime encodeMessage(.{ 235 | Tag.init(.SGROUP, 201), // Group.Data .SGROUP 236 | Tag.init(.VARINT, 202), // Group.Data.group_int32 237 | 202, 238 | Tag.init(.VARINT, 203), // Group.Data.group_uint32 239 | 203, 240 | Tag.init(.EGROUP, 201), // Group.Data .EGROUP 241 | }); 242 | var ctx = protobuf.context(message, talloc); 243 | const m = try ctx.deserialize(&T.descriptor); 244 | defer m.deinit(talloc); 245 | const g = try m.as(T); 246 | try testing.expect(g.has(.data)); 247 | try testing.expect(g.data.has(.group_int32)); 248 | try testing.expectEqual(202, g.data.group_int32); 249 | try testing.expect(g.data.has(.group_uint32)); 250 | try testing.expectEqual(203, g.data.group_uint32); 251 | } 252 | -------------------------------------------------------------------------------- /src/test-serialize.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const testing = std.testing; 4 | const assert = std.debug.assert; 5 | 6 | const pb = @import("protobuf"); 7 | const types = pb.types; 8 | const plugin = pb.plugin; 9 | const descr = pb.descriptor; 10 | const protobuf = pb.protobuf; 11 | const CodeGeneratorRequest = plugin.CodeGeneratorRequest; 12 | const FieldDescriptorProto = descr.FieldDescriptorProto; 13 | const Tag = types.Tag; 14 | const tcommon = pb.testing; 15 | const encodeMessage = tcommon.encodeMessage; 16 | const lengthEncode = tcommon.lengthEncode; 17 | const encodeVarint = tcommon.encodeVarint; 18 | const encodeFloat = tcommon.encodeFloat; 19 | const expectEqual = tcommon.expectEqual; 20 | const testInit = tcommon.testInit; 21 | const String = pb.extern_types.String; 22 | 23 | const talloc = testing.allocator; 24 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 25 | const tarena = arena.allocator(); 26 | 27 | test "basic ser" { 28 | var data = descr.FieldOptions.init(); 29 | data.set(.ctype, .STRING); 30 | data.set(.lazy, true); 31 | var ui = descr.UninterpretedOption.init(); 32 | ui.set(.identifier_value, String.init("ident")); 33 | ui.set(.positive_int_value, 42); 34 | ui.set(.negative_int_value, -42); 35 | ui.set(.double_value, 42); 36 | try data.uninterpreted_option.append(talloc, &ui); 37 | data.setPresent(.uninterpreted_option); 38 | defer data.uninterpreted_option.deinit(talloc); 39 | var buf = std.ArrayList(u8).init(talloc); 40 | defer buf.deinit(); 41 | try protobuf.serialize(&data.base, buf.writer()); 42 | 43 | const message = comptime encodeMessage(.{ 44 | Tag.init(.VARINT, 1), // FieldOptions.ctype 45 | @intFromEnum(descr.FieldOptions.CType.STRING), 46 | Tag.init(.VARINT, 5), // FieldOptions.lazy 47 | true, 48 | Tag.init(.LEN, 999), // FieldOptions.uninterpreted_option 49 | lengthEncode(.{ 50 | Tag.init(.LEN, 3), // UninterpretedOption.identifier_value 51 | lengthEncode(.{"ident"}), 52 | Tag.init(.VARINT, 4), // UninterpretedOption.positive_int_value 53 | encodeVarint(u8, 42), 54 | Tag.init(.VARINT, 5), // UninterpretedOption.negative_int_value 55 | encodeVarint(i64, -42), 56 | Tag.init(.I64, 6), // UninterpretedOption.double_value 57 | encodeFloat(f64, 42.0), 58 | }), 59 | }); 60 | 61 | try testing.expectEqualSlices(u8, message, buf.items); 62 | } 63 | 64 | test "packed repeated ser 1" { 65 | // from https://developers.google.com/protocol-buffers/docs/encoding#packed 66 | const Test5 = extern struct { 67 | base: pb.types.Message, 68 | // repeated int32 f = 6 [packed=true]; 69 | f: pb.extern_types.ArrayListMut(i32) = .{}, 70 | 71 | pub const field_ids = [_]c_uint{6}; 72 | pub const opt_field_ids = [_]c_uint{}; 73 | pub usingnamespace pb.types.MessageMixins(@This()); 74 | 75 | pub const field_descriptors = [_]pb.types.FieldDescriptor{ 76 | pb.types.FieldDescriptor.init( 77 | "f", 78 | 6, 79 | .LABEL_REPEATED, 80 | .TYPE_INT32, 81 | @offsetOf(@This(), "f"), 82 | null, 83 | null, 84 | @intFromEnum(pb.types.FieldDescriptor.FieldFlag.FLAG_PACKED), 85 | ), 86 | }; 87 | }; 88 | 89 | var data = Test5.init(); 90 | defer data.f.deinit(talloc); 91 | try data.f.appendSlice(talloc, &.{ 3, 270, 86942 }); 92 | data.setPresent(.f); 93 | 94 | var buf = std.ArrayList(u8).init(talloc); 95 | defer buf.deinit(); 96 | try protobuf.serialize(&data.base, buf.writer()); 97 | 98 | var buf2: [64]u8 = undefined; 99 | const actual = try std.fmt.bufPrint(&buf2, "{}", .{std.fmt.fmtSliceHexLower(buf.items)}); 100 | try testing.expectEqualStrings("3206038e029ea705", actual); 101 | } 102 | 103 | test "packed repeated ser 2" { 104 | var data = descr.FileDescriptorProto.init(); 105 | // defer data.base.deinit(talloc); 106 | // ^ don't do this as it tries to free list strings and the bytes of data 107 | // which are non-heap allocated memory here. 108 | var deps: pb.extern_types.ArrayListMut(String) = .{}; 109 | try deps.append(talloc, String.init("dep1")); 110 | defer deps.deinit(talloc); 111 | data.set(.dependency, deps); 112 | var pubdeps: pb.extern_types.ArrayListMut(i32) = .{}; 113 | defer pubdeps.deinit(talloc); 114 | try pubdeps.appendSlice(talloc, &.{ 0, 1, 2 }); 115 | data.set(.public_dependency, pubdeps); 116 | 117 | var buf = std.ArrayList(u8).init(talloc); 118 | defer buf.deinit(); 119 | try protobuf.serialize(&data.base, buf.writer()); 120 | 121 | var ctx = pb.protobuf.context(buf.items, talloc); 122 | const m = try ctx.deserialize(&descr.FileDescriptorProto.descriptor); 123 | defer m.deinit(talloc); 124 | const T = descr.FileDescriptorProto; 125 | const data2 = try m.as(T); 126 | try expectEqual(T, data, data2.*); 127 | } 128 | 129 | test "ser all_types.proto" { 130 | const all_types = @import("generated").all_types; 131 | const T = all_types.All; 132 | 133 | // init the all_types object 134 | var data = try testInit(T, null, tarena); 135 | try testing.expectEqual(@as(usize, 1), data.oneof_fields.len); 136 | try testing.expect(data.oneof_fields.items[0].base.hasFieldId(111)); 137 | 138 | // serialize the object to buf 139 | var buf = std.ArrayList(u8).init(talloc); 140 | defer buf.deinit(); 141 | try protobuf.serialize(&data.base, buf.writer()); 142 | 143 | // deserialize from buf and check equality 144 | var ctx = protobuf.context(buf.items, talloc); 145 | const m = try ctx.deserialize(&T.descriptor); 146 | defer m.deinit(talloc); 147 | const data2 = try m.as(T); 148 | try expectEqual(T, data, data2.*); 149 | 150 | // serialize m to buf2 and verify buf and buf2 are equal 151 | var buf2 = std.ArrayList(u8).init(talloc); 152 | defer buf2.deinit(); 153 | try protobuf.serialize(m, buf2.writer()); 154 | try testing.expectEqualStrings(buf.items, buf2.items); 155 | } 156 | 157 | test "ser oneof-2.proto" { 158 | const oneof_2 = @import("generated").oneof_2; 159 | const T = oneof_2.TestAllTypesProto3; 160 | 161 | // init the all_types object 162 | var data = try testInit(T, null, tarena); 163 | try testing.expect(data.base.hasFieldId(111)); 164 | try testing.expect(!data.has(.oneof_field__oneof_nested_message)); 165 | 166 | // // serialize the object to buf 167 | var buf = std.ArrayList(u8).init(talloc); 168 | defer buf.deinit(); 169 | try protobuf.serialize(&data.base, buf.writer()); 170 | 171 | // deserialize from buf and check equality 172 | var ctx = protobuf.context(buf.items, talloc); 173 | const m = try ctx.deserialize(&T.descriptor); 174 | defer m.deinit(talloc); 175 | const data2 = try m.as(T); 176 | try expectEqual(T, data, data2.*); 177 | 178 | // serialize m to buf2 and verify buf and buf2 are equal 179 | var buf2 = std.ArrayList(u8).init(talloc); 180 | defer buf2.deinit(); 181 | try protobuf.serialize(m, buf2.writer()); 182 | try testing.expectEqualStrings(buf.items, buf2.items); 183 | } 184 | 185 | test "ser group round trip" { 186 | const group = @import("generated").group; 187 | const T = group.Grouped; 188 | 189 | var g = T.init(); 190 | 191 | // .data field 192 | var data = T.Data.init(); 193 | g.set(.data, &data); 194 | data.set(.group_int32, 202); 195 | data.set(.group_uint32, 203); 196 | 197 | // .data2 field 198 | var item = T.Data2.init(); 199 | item.set(.group2_int32, 212); 200 | item.set(.group2_uint32, 213); 201 | var data2list = pb.extern_types.ArrayListMut(*T.Data2){}; 202 | defer data2list.deinit(talloc); 203 | try data2list.append(talloc, &item); 204 | g.set(.data2, data2list); 205 | 206 | // ser g 207 | var buf = std.ArrayList(u8).init(talloc); 208 | defer buf.deinit(); 209 | try pb.protobuf.serialize(&g.base, buf.writer()); 210 | 211 | // deser ser g 212 | var ctx = protobuf.context(buf.items, talloc); 213 | const m = try ctx.deserialize(&T.descriptor); 214 | defer m.deinit(talloc); 215 | const g2 = try m.as(T); 216 | try testing.expect(g2.has(.data)); 217 | try testing.expect(g2.has(.data2)); 218 | try testing.expect(g2.data2.len == 1); 219 | 220 | // ser deser ser g 221 | var buf2 = std.ArrayList(u8).init(talloc); 222 | defer buf2.deinit(); 223 | try pb.protobuf.serialize(m, buf2.writer()); 224 | 225 | try testing.expectEqualStrings(buf.items, buf2.items); 226 | } 227 | 228 | test "http example" { 229 | // this test is only verifies that serialize()'s error set supports 230 | // more than just std.fs.File errors 231 | 232 | const oneof_2 = @import("generated").oneof_2; 233 | var data = oneof_2.TestAllTypesProto3.init(); 234 | var http_req: std.http.Client.Request = undefined; 235 | var bufw = std.io.bufferedWriter(http_req.writer()); 236 | try pb.protobuf.serialize(&data.base, bufw.writer()); 237 | } 238 | -------------------------------------------------------------------------------- /src/tests.zig: -------------------------------------------------------------------------------- 1 | test { 2 | _ = @import("test-deserialize.zig"); 3 | _ = @import("test-serialize.zig"); 4 | _ = @import("test-conformance.zig"); 5 | } 6 | 7 | // $ zig test src/tests.zig --mod protobuf:protobuf:src/lib.zig --mod generated:protobuf:zig-cache/protobuf-zig/lib.zig --deps protobuf,generated 8 | test "readme" { 9 | // Note - the package 'protobuf' below is src/lib.zig. this package must 10 | // include itself. it can be provided in build.zig or on the command line 11 | // as shown below. 12 | const std = @import("std"); 13 | const pb = @import("protobuf"); 14 | const Person = @import("generated").person.Person; 15 | 16 | // serialize to a writer 17 | const alloc = std.testing.allocator; // could be any zig std.mem.Allocator 18 | var zero = Person.initFields(.{ 19 | .id = 1, 20 | .name = pb.extern_types.String.init("zero"), 21 | .kind = .NONE, 22 | }); 23 | zero.set(.id, 0); 24 | var buf = std.ArrayList(u8).init(alloc); 25 | defer buf.deinit(); 26 | try pb.protobuf.serialize(&zero.base, buf.writer()); 27 | 28 | // deserialize from a buffer 29 | var ctx = pb.protobuf.context(buf.items, alloc); 30 | const message = try ctx.deserialize(&Person.descriptor); 31 | defer message.deinit(alloc); 32 | var zero_copy = try message.as(Person); 33 | 34 | // test that they're equal 35 | try std.testing.expect(zero_copy.has(.id)); 36 | try std.testing.expectEqual(zero.id, zero_copy.id); 37 | try std.testing.expect(zero_copy.has(.name)); 38 | try std.testing.expectEqualStrings(zero.name.slice(), zero_copy.name.slice()); 39 | try std.testing.expect(zero_copy.has(.kind)); 40 | try std.testing.expectEqual(zero.kind, zero_copy.kind); 41 | 42 | // serialize to json 43 | // const stderr = std.io.getStdErr().writer(); 44 | const stderr = std.io.null_writer; 45 | try pb.json.serialize(&zero.base, stderr, .{ 46 | .pretty_print = .{ .indent_size = 2 }, 47 | }); 48 | _ = try stderr.write("\n"); 49 | // prints 50 | //{ 51 | // "name": "zero", 52 | // "id": 0, 53 | // "kind": "NONE" 54 | //} 55 | } 56 | -------------------------------------------------------------------------------- /src/types.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub usingnamespace @import("protobuf-types.zig"); 4 | pub usingnamespace @import("meta.zig"); 5 | 6 | pub fn IntegerBitset(comptime len: usize) type { 7 | const l = std.math.ceilPowerOfTwo(usize, @max(len, 1)) catch 8 | unreachable; 9 | return std.meta.Int(.unsigned, @max(8, l)); 10 | } 11 | 12 | /// https://protobuf.dev/programming-guides/encoding/#structure 13 | pub const WireType = enum(u8) { 14 | VARINT = 0, 15 | I64 = 1, 16 | LEN = 2, 17 | SGROUP = 3, 18 | EGROUP = 4, 19 | I32 = 5, 20 | }; 21 | 22 | pub const Tag = extern struct { 23 | wire_type: WireType, 24 | // https://protobuf.dev/programming-guides/proto3/#assigning-field-numbers 25 | /// The smallest field number you can specify is 1, and the largest is 229 26 | field_id: u32, 27 | pub inline fn encode(key: Tag) u32 { 28 | return (key.field_id << 3) | @intFromEnum(key.wire_type); 29 | } 30 | pub fn init(wire_type: WireType, field_id: u32) Tag { 31 | return .{ 32 | .wire_type = wire_type, 33 | .field_id = field_id, 34 | }; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/unescape-octal.zig: -------------------------------------------------------------------------------- 1 | //! 2 | //! This program converts annoying octal conformance error payloads into 3 | //! hex. Example of this conversion the command line: 4 | //! $ zig run src/conformance-helper.zig 5 | //! \222\001\013\022\010\341!\030\341!\370\001\341! 6 | //! => 7 | //! 92010b1208e12118e121f801e121 8 | //! 9 | const std = @import("std"); 10 | const assert = std.debug.assert; 11 | 12 | // supports escape sequences \n, \r, \\, \t, \NNN (octal) 13 | pub fn parseEscapeSequence(slice: []const u8, offsetp: *usize) !u8 { 14 | const offset = offsetp.*; 15 | assert(slice.len > offset); 16 | assert(slice[offset] == '\\'); 17 | 18 | if (slice.len == offset + 1) return error.InvalidEscape; 19 | 20 | var skiplen: u8 = 2; 21 | defer offsetp.* += skiplen; 22 | return switch (slice[offset + 1]) { 23 | 'n' => '\n', 24 | 'r' => '\r', 25 | '\\' => '\\', 26 | 't' => '\t', 27 | '0'...'7' => blk: { 28 | const octstr = slice[offset + 1 .. offset + 4]; 29 | assert(octstr.len == 3); 30 | const oct = try std.fmt.parseUnsigned(u8, octstr, 8); 31 | skiplen += 2; 32 | break :blk oct; 33 | }, 34 | else => blk: { 35 | std.log.err("invalid escape '{c}'", .{slice[offset + 1]}); 36 | break :blk error.InvalidEscape; 37 | }, 38 | }; 39 | } 40 | 41 | /// converts escape sequences in-place 42 | pub fn parseEscapes(content_: []u8) ![]u8 { 43 | var content = content_; 44 | var fbs = std.io.fixedBufferStream(content); 45 | const writer = fbs.writer(); 46 | 47 | var index: usize = 0; 48 | while (true) { 49 | if (index >= content.len) 50 | return content[0..fbs.pos]; 51 | 52 | const b = content[index]; 53 | 54 | switch (b) { 55 | '\\' => try writer.writeByte(try parseEscapeSequence(content, &index)), 56 | else => { 57 | try writer.writeByte(b); 58 | index += 1; 59 | }, 60 | } 61 | } 62 | } 63 | const Format = enum { hex, binary }; 64 | pub fn main() !void { 65 | // var input = "\\202\\002\\001\\200".*; 66 | const stdin = std.io.getStdIn().reader(); 67 | 68 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 69 | const alloc = arena.allocator(); 70 | var args = try std.process.argsWithAllocator(alloc); 71 | _ = args.next(); 72 | 73 | // parse args: format 74 | var format = Format.hex; 75 | while (args.next()) |arg| { 76 | if (std.mem.startsWith(u8, arg, "--format")) { 77 | const s = args.next() orelse { 78 | std.log.err("missing --format arg. accepted values {s}", .{std.meta.fieldNames(Format)}); 79 | return error.Arg; 80 | }; 81 | format = std.meta.stringToEnum(Format, s) orelse { 82 | std.log.err("invalid --format arg '{s}'. accepted values {s}", .{ s, std.meta.fieldNames(Format) }); 83 | return error.Arg; 84 | }; 85 | } else if (std.mem.eql(u8, arg, "-b")) { 86 | format = .binary; 87 | } else { 88 | unreachable; 89 | } 90 | } 91 | var input = try stdin.readAllAlloc(alloc, std.math.maxInt(u32)); 92 | 93 | // trim newlines. maybe should only trim one? 94 | while (input.len > 0) : (input.len -= 1) { 95 | if (input[input.len - 1] != '\n') break; 96 | } 97 | const escaped = try parseEscapes(input); 98 | const stdout = std.io.getStdOut().writer(); 99 | if (format == .hex) 100 | try stdout.print("\n{}\n", .{std.fmt.fmtSliceHexLower(escaped)}) 101 | else 102 | try stdout.print("{s}", .{escaped}); 103 | } 104 | --------------------------------------------------------------------------------