├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── Makefile ├── README.md ├── dune-project ├── pb-plugin.opam ├── pb-plugin.opam.template ├── pb-plugin ├── misc │ ├── README.md │ ├── messages.proto │ └── messages_unnested.proto ├── src │ ├── dune │ ├── pb_plugin.ml │ ├── protobuf_reified.ml │ └── protoc_messages.ml └── test │ ├── .gitignore │ ├── __init__.py │ ├── comprehensive.proto │ ├── dune │ ├── test.ml │ ├── test_gen.py │ └── test_read.py ├── pb.opam ├── pb.opam.template └── pb ├── src ├── dune ├── pb.ml ├── pb.mli ├── wire_type.ml └── wire_type.mli └── test ├── dune ├── test.ml ├── test.proto ├── test_gen.py ├── test_messages.ml └── test_read.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | _build/ 3 | *.native 4 | *.install 5 | *.merlin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: bionic 3 | before_install: 4 | - sudo apt-get -qq update 5 | - sudo apt-get install -y protobuf-compiler python3-pip 6 | - sudo pip3 install protobuf 7 | install: 8 | - wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-opam.sh 9 | env: 10 | global: 11 | - PINS="pb.dev:. pb-plugin.dev:." 12 | - PACKAGE="pb" OCAML_VERSION=4.03 13 | - PACKAGE="pb" OCAML_VERSION=4.04 14 | - PACKAGE="pb" OCAML_VERSION=4.05 15 | - PACKAGE="pb" OCAML_VERSION=4.06 16 | - PACKAGE="pb" OCAML_VERSION=4.07 17 | - PACKAGE="pb" OCAML_VERSION=4.08 18 | - PACKAGE="pb-plugin" OCAML_VERSION=4.03 19 | - PACKAGE="pb-plugin" OCAML_VERSION=4.04 20 | - PACKAGE="pb-plugin" OCAML_VERSION=4.05 21 | - PACKAGE="pb-plugin" OCAML_VERSION=4.06 22 | - PACKAGE="pb-plugin" OCAML_VERSION=4.07 23 | - PACKAGE="pb-plugin" OCAML_VERSION=4.08 24 | script: 25 | - bash -ex .travis-opam.sh 26 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (12/3/2019) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Jeremy Yallop . 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | dune build 3 | 4 | clean: 5 | dune clean 6 | 7 | test: 8 | dune runtest 9 | 10 | .PHONY: all clean test 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pb, a library for describing Protobuf messages 2 | 3 | `pb` is an OCaml library for describing and serializing [Protocol buffers 4 | (Protobuf)][protobuf] messages. 5 | 6 | Message descriptions can be written by hand, or generated from `.proto` files 7 | using the `pb-plugin` protoc compiler plugin. 8 | 9 | ## Describing messages 10 | 11 | Protocol buffers provide both a file format for describing messages and a 12 | serialization (wire) format. The `pb` library supports only the wire format, but 13 | there is a straightforward mapping from the file format into `pb` code. Here is 14 | a description of a [Protobuf message][protobuf-message] with two fields, a 15 | `number` field with the [tag][protobuf-tag] `1`, and a `PhoneType` field with 16 | the tag `2` and default value `HOME`: 17 | 18 | 19 | ```protobuf 20 | message PhoneNumber { 21 | required string number = 1; 22 | optional PhoneType type = 2 [default = HOME]; 23 | } 24 | ``` 25 | 26 | And here is the equivalent `pb` code that defines the `PhoneNumber` message: 27 | 28 | ```ocaml 29 | module PhoneNumber = (val message "PhoneNumber") 30 | let number = PhoneNumber.required string "number" 1 31 | let type_ = PhoneNumber.optional "type" 2 ~default:home 32 | ``` 33 | 34 | The `type` field of the `PhoneNumber` message has the type `PhoneType`, an 35 | [enumeration value][protobuf-enum]. Here is a description of the `PhoneType` 36 | enumeration with its three values: 37 | 38 | ```protobuf 39 | enum PhoneType { 40 | MOBILE = 0; 41 | HOME = 1; 42 | WORK = 2; 43 | } 44 | ``` 45 | 46 | And here is the equivalent `pb` code that defines the `PhoneType` enumeration: 47 | 48 | 49 | ```ocaml 50 | module PhoneType = (val enum "PhoneType") 51 | let mobile = PhoneType.constant "MOBILE" 0l 52 | let home = PhoneType.constant "HOME" 1l 53 | let work = PhoneType.constant "WORK" 2l 54 | ``` 55 | 56 | ## Serializing and deserializing 57 | 58 | Messages described by `pb` can be serialized using the [Faraday][faraday] 59 | serialization library. The following code creates a `PhoneNumber` message, 60 | assigns values to its two fields, and writes it to a Faraday serializer: 61 | 62 | ```ocaml 63 | let pn = create PhoneNumber.t in 64 | setf pn number ("+1-541-754-3010"); 65 | setf pn type_ work; 66 | write pn 67 | ``` 68 | 69 | Messages can be deserialized (parsed) using the [Angstrom][angstrom] 70 | parser-combinator library. The following code reads a message using Angstrom and 71 | retrieves the values of its fields: 72 | 73 | 74 | ```ocaml 75 | let pn = match Angstrom.parse_string (read PhoneNumber.t) s) with 76 | | Ok m -> m 77 | | Error s -> failwith s 78 | in (getf pn number, getf pn type_) 79 | ``` 80 | 81 | ## Pretty-printing 82 | 83 | Messages can also be pretty-printed: 84 | 85 | ```ocaml 86 | pp_msg PhoneNumber.t Format.std_formatter pn 87 | ``` 88 | 89 | *** 90 | 91 | [![Travis build Status](https://travis-ci.org/yallop/ocaml-pb.svg?branch=master)](https://travis-ci.org/yallop/ocaml-pb) 92 | 93 | [protobuf]: https://developers.google.com/protocol-buffers/ 94 | [angstrom]: https://github.com/inhabitedtype/angstrom 95 | [faraday]: https://github.com/inhabitedtype/faraday 96 | [protobuf-enum]: https://developers.google.com/protocol-buffers/docs/proto#enum 97 | [protobuf-message]: https://developers.google.com/protocol-buffers/docs/proto#simple 98 | [protobuf-tag]: https://developers.google.com/protocol-buffers/docs/proto#assigning-tags 99 | [protobuf-scalar]: https://developers.google.com/protocol-buffers/docs/proto#scalar 100 | [pb-plugin]: https://github.com/yallop/ocaml-pb-plugin 101 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.11) 2 | (name pb) 3 | 4 | (generate_opam_files true) 5 | 6 | (implicit_transitive_deps false) 7 | 8 | (license MIT) 9 | 10 | (maintainers 11 | "Jeremy Yallop " 12 | "Rudi Grinberg ") 13 | (authors "Jeremy Yallop ") 14 | (source (github yallop/ocaml-pb)) 15 | 16 | (package 17 | (name pb) 18 | (depends 19 | (ocaml (>= 4.03)) 20 | (dune (>= 1.11)) 21 | integers 22 | (angstrom (>= 0.10.0)) 23 | faraday 24 | (ounit (and :with-test (>= 2.0)))) 25 | (synopsis "Library for describing Protobuf messages") 26 | (description "Library for describing Protobuf messages")) 27 | 28 | (package 29 | (name pb-plugin) 30 | (depends 31 | (ocaml (>= 4.03)) 32 | pb 33 | (dune (>= 1.11)) 34 | integers 35 | batteries 36 | (angstrom (>= 0.10.0)) 37 | faraday 38 | (ounit (and :with-test (>= 2.0)))) 39 | (synopsis "Plugin for generating pb protobuf message descriptions") 40 | (description "Plugin for generating pb protobuf message descriptions")) 41 | -------------------------------------------------------------------------------- /pb-plugin.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Plugin for generating pb protobuf message descriptions" 4 | description: "Plugin for generating pb protobuf message descriptions" 5 | maintainer: [ 6 | "Jeremy Yallop " "Rudi Grinberg " 7 | ] 8 | authors: ["Jeremy Yallop "] 9 | license: "MIT" 10 | homepage: "https://github.com/yallop/ocaml-pb" 11 | bug-reports: "https://github.com/yallop/ocaml-pb/issues" 12 | depends: [ 13 | "ocaml" {>= "4.03"} 14 | "pb" 15 | "dune" {>= "1.11"} 16 | "integers" 17 | "batteries" 18 | "angstrom" {>= "0.14.0"} 19 | "faraday" 20 | "ounit" {with-test & >= "2.0"} 21 | ] 22 | dev-repo: "git+https://github.com/yallop/ocaml-pb.git" 23 | build: [ 24 | ["dune" "subst"] {pinned} 25 | [ 26 | "dune" 27 | "build" 28 | "-p" 29 | name 30 | "-j" 31 | jobs 32 | "@install" 33 | "@doc" {with-doc} 34 | ] 35 | ] 36 | -------------------------------------------------------------------------------- /pb-plugin.opam.template: -------------------------------------------------------------------------------- 1 | build: [ 2 | ["dune" "subst"] {pinned} 3 | [ 4 | "dune" 5 | "build" 6 | "-p" 7 | name 8 | "-j" 9 | jobs 10 | "@install" 11 | "@doc" {with-doc} 12 | ] 13 | ] 14 | -------------------------------------------------------------------------------- /pb-plugin/misc/README.md: -------------------------------------------------------------------------------- 1 | Various `.proto` files: 2 | 3 | * [`messages.proto`](messages.proto): 4 | The protobuf messages that define the interface between the `protoc` 5 | compiler and plugins. 6 | 7 | * [`messages_unnested.proto`](messages_unnested.proto): 8 | An variant of `messages.proto` without nested messages, which are 9 | not currently supported by this plugin. 10 | -------------------------------------------------------------------------------- /pb-plugin/misc/messages.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 | // Based on original Protocol Buffers design by 33 | // Sanjay Ghemawat, Jeff Dean, and others. 34 | // 35 | // The messages in this file describe the definitions found in .proto files. 36 | // A valid .proto file can be translated directly to a FileDescriptorProto 37 | // without any other information (e.g. without reading its imports). 38 | 39 | 40 | syntax = "proto2"; 41 | 42 | package google.protobuf; 43 | option go_package = "descriptor"; 44 | option java_package = "com.google.protobuf"; 45 | option java_outer_classname = "DescriptorProtos"; 46 | option csharp_namespace = "Google.Protobuf.Reflection"; 47 | option objc_class_prefix = "GPB"; 48 | 49 | // descriptor.proto must be optimized for speed because reflection-based 50 | // algorithms don't work during bootstrapping. 51 | option optimize_for = SPEED; 52 | 53 | // The protocol compiler can output a FileDescriptorSet containing the .proto 54 | // files it parses. 55 | message FileDescriptorSet { 56 | repeated FileDescriptorProto file = 1; 57 | } 58 | 59 | // Describes a complete .proto file. 60 | message FileDescriptorProto { 61 | optional string name = 1; // file name, relative to root of source tree 62 | optional string package = 2; // e.g. "foo", "foo.bar", etc. 63 | 64 | // Names of files imported by this file. 65 | repeated string dependency = 3; 66 | // Indexes of the public imported files in the dependency list above. 67 | repeated int32 public_dependency = 10; 68 | // Indexes of the weak imported files in the dependency list. 69 | // For Google-internal migration only. Do not use. 70 | repeated int32 weak_dependency = 11; 71 | 72 | // All top-level definitions in this file. 73 | repeated DescriptorProto message_type = 4; 74 | repeated EnumDescriptorProto enum_type = 5; 75 | repeated ServiceDescriptorProto service = 6; 76 | repeated FieldDescriptorProto extension = 7; 77 | 78 | optional FileOptions options = 8; 79 | 80 | // This field contains optional information about the original source code. 81 | // You may safely remove this entire field without harming runtime 82 | // functionality of the descriptors -- the information is needed only by 83 | // development tools. 84 | optional SourceCodeInfo source_code_info = 9; 85 | 86 | // The syntax of the proto file. 87 | // The supported values are "proto2" and "proto3". 88 | optional string syntax = 12; 89 | } 90 | 91 | // Describes a message type. 92 | message DescriptorProto { 93 | optional string name = 1; 94 | 95 | repeated FieldDescriptorProto field = 2; 96 | repeated FieldDescriptorProto extension = 6; 97 | 98 | repeated DescriptorProto nested_type = 3; 99 | repeated EnumDescriptorProto enum_type = 4; 100 | 101 | message ExtensionRange { 102 | optional int32 start = 1; 103 | optional int32 end = 2; 104 | } 105 | repeated ExtensionRange extension_range = 5; 106 | 107 | repeated OneofDescriptorProto oneof_decl = 8; 108 | 109 | optional MessageOptions options = 7; 110 | 111 | // Range of reserved tag numbers. Reserved tag numbers may not be used by 112 | // fields or extension ranges in the same message. Reserved ranges may 113 | // not overlap. 114 | message ReservedRange { 115 | optional int32 start = 1; // Inclusive. 116 | optional int32 end = 2; // Exclusive. 117 | } 118 | repeated ReservedRange reserved_range = 9; 119 | // Reserved field names, which may not be used by fields in the same message. 120 | // A given name may only be reserved once. 121 | repeated string reserved_name = 10; 122 | } 123 | 124 | // Describes a field within a message. 125 | message FieldDescriptorProto { 126 | enum Type { 127 | // 0 is reserved for errors. 128 | // Order is weird for historical reasons. 129 | TYPE_DOUBLE = 1; 130 | TYPE_FLOAT = 2; 131 | // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if 132 | // negative values are likely. 133 | TYPE_INT64 = 3; 134 | TYPE_UINT64 = 4; 135 | // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if 136 | // negative values are likely. 137 | TYPE_INT32 = 5; 138 | TYPE_FIXED64 = 6; 139 | TYPE_FIXED32 = 7; 140 | TYPE_BOOL = 8; 141 | TYPE_STRING = 9; 142 | TYPE_GROUP = 10; // Tag-delimited aggregate. 143 | TYPE_MESSAGE = 11; // Length-delimited aggregate. 144 | 145 | // New in version 2. 146 | TYPE_BYTES = 12; 147 | TYPE_UINT32 = 13; 148 | TYPE_ENUM = 14; 149 | TYPE_SFIXED32 = 15; 150 | TYPE_SFIXED64 = 16; 151 | TYPE_SINT32 = 17; // Uses ZigZag encoding. 152 | TYPE_SINT64 = 18; // Uses ZigZag encoding. 153 | }; 154 | 155 | enum Label { 156 | // 0 is reserved for errors 157 | LABEL_OPTIONAL = 1; 158 | LABEL_REQUIRED = 2; 159 | LABEL_REPEATED = 3; 160 | }; 161 | 162 | optional string name = 1; 163 | optional int32 number = 3; 164 | optional Label label = 4; 165 | 166 | // If type_name is set, this need not be set. If both this and type_name 167 | // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. 168 | optional Type type = 5; 169 | 170 | // For message and enum types, this is the name of the type. If the name 171 | // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping 172 | // rules are used to find the type (i.e. first the nested types within this 173 | // message are searched, then within the parent, on up to the root 174 | // namespace). 175 | optional string type_name = 6; 176 | 177 | // For extensions, this is the name of the type being extended. It is 178 | // resolved in the same manner as type_name. 179 | optional string extendee = 2; 180 | 181 | // For numeric types, contains the original text representation of the value. 182 | // For booleans, "true" or "false". 183 | // For strings, contains the default text contents (not escaped in any way). 184 | // For bytes, contains the C escaped value. All bytes >= 128 are escaped. 185 | // TODO(kenton): Base-64 encode? 186 | optional string default_value = 7; 187 | 188 | // If set, gives the index of a oneof in the containing type's oneof_decl 189 | // list. This field is a member of that oneof. 190 | optional int32 oneof_index = 9; 191 | 192 | // JSON name of this field. The value is set by protocol compiler. If the 193 | // user has set a "json_name" option on this field, that option's value 194 | // will be used. Otherwise, it's deduced from the field's name by converting 195 | // it to camelCase. 196 | optional string json_name = 10; 197 | 198 | optional FieldOptions options = 8; 199 | } 200 | 201 | // Describes a oneof. 202 | message OneofDescriptorProto { 203 | optional string name = 1; 204 | optional OneofOptions options = 2; 205 | } 206 | 207 | // Describes an enum type. 208 | message EnumDescriptorProto { 209 | optional string name = 1; 210 | 211 | repeated EnumValueDescriptorProto value = 2; 212 | 213 | optional EnumOptions options = 3; 214 | } 215 | 216 | // Describes a value within an enum. 217 | message EnumValueDescriptorProto { 218 | optional string name = 1; 219 | optional int32 number = 2; 220 | 221 | optional EnumValueOptions options = 3; 222 | } 223 | 224 | // Describes a service. 225 | message ServiceDescriptorProto { 226 | optional string name = 1; 227 | repeated MethodDescriptorProto method = 2; 228 | 229 | optional ServiceOptions options = 3; 230 | } 231 | 232 | // Describes a method of a service. 233 | message MethodDescriptorProto { 234 | optional string name = 1; 235 | 236 | // Input and output type names. These are resolved in the same way as 237 | // FieldDescriptorProto.type_name, but must refer to a message type. 238 | optional string input_type = 2; 239 | optional string output_type = 3; 240 | 241 | optional MethodOptions options = 4; 242 | 243 | // Identifies if client streams multiple client messages 244 | optional bool client_streaming = 5 [default=false]; 245 | // Identifies if server streams multiple server messages 246 | optional bool server_streaming = 6 [default=false]; 247 | } 248 | 249 | 250 | // =================================================================== 251 | // Options 252 | 253 | // Each of the definitions above may have "options" attached. These are 254 | // just annotations which may cause code to be generated slightly differently 255 | // or may contain hints for code that manipulates protocol messages. 256 | // 257 | // Clients may define custom options as extensions of the *Options messages. 258 | // These extensions may not yet be known at parsing time, so the parser cannot 259 | // store the values in them. Instead it stores them in a field in the *Options 260 | // message called uninterpreted_option. This field must have the same name 261 | // across all *Options messages. We then use this field to populate the 262 | // extensions when we build a descriptor, at which point all protos have been 263 | // parsed and so all extensions are known. 264 | // 265 | // Extension numbers for custom options may be chosen as follows: 266 | // * For options which will only be used within a single application or 267 | // organization, or for experimental options, use field numbers 50000 268 | // through 99999. It is up to you to ensure that you do not use the 269 | // same number for multiple options. 270 | // * For options which will be published and used publicly by multiple 271 | // independent entities, e-mail protobuf-global-extension-registry@google.com 272 | // to reserve extension numbers. Simply provide your project name (e.g. 273 | // Objective-C plugin) and your project website (if available) -- there's no 274 | // need to explain how you intend to use them. Usually you only need one 275 | // extension number. You can declare multiple options with only one extension 276 | // number by putting them in a sub-message. See the Custom Options section of 277 | // the docs for examples: 278 | // https://developers.google.com/protocol-buffers/docs/proto#options 279 | // If this turns out to be popular, a web service will be set up 280 | // to automatically assign option numbers. 281 | 282 | 283 | message FileOptions { 284 | 285 | // Sets the Java package where classes generated from this .proto will be 286 | // placed. By default, the proto package is used, but this is often 287 | // inappropriate because proto packages do not normally start with backwards 288 | // domain names. 289 | optional string java_package = 1; 290 | 291 | 292 | // If set, all the classes from the .proto file are wrapped in a single 293 | // outer class with the given name. This applies to both Proto1 294 | // (equivalent to the old "--one_java_file" option) and Proto2 (where 295 | // a .proto always translates to a single class, but you may want to 296 | // explicitly choose the class name). 297 | optional string java_outer_classname = 8; 298 | 299 | // If set true, then the Java code generator will generate a separate .java 300 | // file for each top-level message, enum, and service defined in the .proto 301 | // file. Thus, these types will *not* be nested inside the outer class 302 | // named by java_outer_classname. However, the outer class will still be 303 | // generated to contain the file's getDescriptor() method as well as any 304 | // top-level extensions defined in the file. 305 | optional bool java_multiple_files = 10 [default=false]; 306 | 307 | // This option does nothing. 308 | optional bool java_generate_equals_and_hash = 20 [deprecated=true]; 309 | 310 | // If set true, then the Java2 code generator will generate code that 311 | // throws an exception whenever an attempt is made to assign a non-UTF-8 312 | // byte sequence to a string field. 313 | // Message reflection will do the same. 314 | // However, an extension field still accepts non-UTF-8 byte sequences. 315 | // This option has no effect on when used with the lite runtime. 316 | optional bool java_string_check_utf8 = 27 [default=false]; 317 | 318 | 319 | // Generated classes can be optimized for speed or code size. 320 | enum OptimizeMode { 321 | SPEED = 1; // Generate complete code for parsing, serialization, 322 | // etc. 323 | CODE_SIZE = 2; // Use ReflectionOps to implement these methods. 324 | LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. 325 | } 326 | optional OptimizeMode optimize_for = 9 [default=SPEED]; 327 | 328 | // Sets the Go package where structs generated from this .proto will be 329 | // placed. If omitted, the Go package will be derived from the following: 330 | // - The basename of the package import path, if provided. 331 | // - Otherwise, the package statement in the .proto file, if present. 332 | // - Otherwise, the basename of the .proto file, without extension. 333 | optional string go_package = 11; 334 | 335 | 336 | 337 | // Should generic services be generated in each language? "Generic" services 338 | // are not specific to any particular RPC system. They are generated by the 339 | // main code generators in each language (without additional plugins). 340 | // Generic services were the only kind of service generation supported by 341 | // early versions of google.protobuf. 342 | // 343 | // Generic services are now considered deprecated in favor of using plugins 344 | // that generate code specific to your particular RPC system. Therefore, 345 | // these default to false. Old code which depends on generic services should 346 | // explicitly set them to true. 347 | optional bool cc_generic_services = 16 [default=false]; 348 | optional bool java_generic_services = 17 [default=false]; 349 | optional bool py_generic_services = 18 [default=false]; 350 | 351 | // Is this file deprecated? 352 | // Depending on the target platform, this can emit Deprecated annotations 353 | // for everything in the file, or it will be completely ignored; in the very 354 | // least, this is a formalization for deprecating files. 355 | optional bool deprecated = 23 [default=false]; 356 | 357 | // Enables the use of arenas for the proto messages in this file. This applies 358 | // only to generated classes for C++. 359 | optional bool cc_enable_arenas = 31 [default=false]; 360 | 361 | 362 | // Sets the objective c class prefix which is prepended to all objective c 363 | // generated classes from this .proto. There is no default. 364 | optional string objc_class_prefix = 36; 365 | 366 | // Namespace for generated classes; defaults to the package. 367 | optional string csharp_namespace = 37; 368 | 369 | // By default Swift generators will take the proto package and CamelCase it 370 | // replacing '.' with underscore and use that to prefix the types/symbols 371 | // defined. When this options is provided, they will use this value instead 372 | // to prefix the types/symbols defined. 373 | optional string swift_prefix = 39; 374 | 375 | // The parser stores options it doesn't recognize here. See above. 376 | repeated UninterpretedOption uninterpreted_option = 999; 377 | 378 | // Clients can define custom options in extensions of this message. See above. 379 | extensions 1000 to max; 380 | 381 | reserved 38; 382 | } 383 | 384 | message MessageOptions { 385 | // Set true to use the old proto1 MessageSet wire format for extensions. 386 | // This is provided for backwards-compatibility with the MessageSet wire 387 | // format. You should not use this for any other reason: It's less 388 | // efficient, has fewer features, and is more complicated. 389 | // 390 | // The message must be defined exactly as follows: 391 | // message Foo { 392 | // option message_set_wire_format = true; 393 | // extensions 4 to max; 394 | // } 395 | // Note that the message cannot have any defined fields; MessageSets only 396 | // have extensions. 397 | // 398 | // All extensions of your type must be singular messages; e.g. they cannot 399 | // be int32s, enums, or repeated messages. 400 | // 401 | // Because this is an option, the above two restrictions are not enforced by 402 | // the protocol compiler. 403 | optional bool message_set_wire_format = 1 [default=false]; 404 | 405 | // Disables the generation of the standard "descriptor()" accessor, which can 406 | // conflict with a field of the same name. This is meant to make migration 407 | // from proto1 easier; new code should avoid fields named "descriptor". 408 | optional bool no_standard_descriptor_accessor = 2 [default=false]; 409 | 410 | // Is this message deprecated? 411 | // Depending on the target platform, this can emit Deprecated annotations 412 | // for the message, or it will be completely ignored; in the very least, 413 | // this is a formalization for deprecating messages. 414 | optional bool deprecated = 3 [default=false]; 415 | 416 | // Whether the message is an automatically generated map entry type for the 417 | // maps field. 418 | // 419 | // For maps fields: 420 | // map map_field = 1; 421 | // The parsed descriptor looks like: 422 | // message MapFieldEntry { 423 | // option map_entry = true; 424 | // optional KeyType key = 1; 425 | // optional ValueType value = 2; 426 | // } 427 | // repeated MapFieldEntry map_field = 1; 428 | // 429 | // Implementations may choose not to generate the map_entry=true message, but 430 | // use a native map in the target language to hold the keys and values. 431 | // The reflection APIs in such implementions still need to work as 432 | // if the field is a repeated message field. 433 | // 434 | // NOTE: Do not set the option in .proto files. Always use the maps syntax 435 | // instead. The option should only be implicitly set by the proto compiler 436 | // parser. 437 | optional bool map_entry = 7; 438 | 439 | reserved 8; // javalite_serializable 440 | 441 | 442 | // The parser stores options it doesn't recognize here. See above. 443 | repeated UninterpretedOption uninterpreted_option = 999; 444 | 445 | // Clients can define custom options in extensions of this message. See above. 446 | extensions 1000 to max; 447 | } 448 | 449 | message FieldOptions { 450 | // The ctype option instructs the C++ code generator to use a different 451 | // representation of the field than it normally would. See the specific 452 | // options below. This option is not yet implemented in the open source 453 | // release -- sorry, we'll try to include it in a future version! 454 | optional CType ctype = 1 [default = STRING]; 455 | enum CType { 456 | // Default mode. 457 | STRING = 0; 458 | 459 | CORD = 1; 460 | 461 | STRING_PIECE = 2; 462 | } 463 | // The packed option can be enabled for repeated primitive fields to enable 464 | // a more efficient representation on the wire. Rather than repeatedly 465 | // writing the tag and type for each element, the entire array is encoded as 466 | // a single length-delimited blob. In proto3, only explicit setting it to 467 | // false will avoid using packed encoding. 468 | optional bool packed = 2; 469 | 470 | // The jstype option determines the JavaScript type used for values of the 471 | // field. The option is permitted only for 64 bit integral and fixed types 472 | // (int64, uint64, sint64, fixed64, sfixed64). By default these types are 473 | // represented as JavaScript strings. This avoids loss of precision that can 474 | // happen when a large value is converted to a floating point JavaScript 475 | // numbers. Specifying JS_NUMBER for the jstype causes the generated 476 | // JavaScript code to use the JavaScript "number" type instead of strings. 477 | // This option is an enum to permit additional types to be added, 478 | // e.g. goog.math.Integer. 479 | optional JSType jstype = 6 [default = JS_NORMAL]; 480 | enum JSType { 481 | // Use the default type. 482 | JS_NORMAL = 0; 483 | 484 | // Use JavaScript strings. 485 | JS_STRING = 1; 486 | 487 | // Use JavaScript numbers. 488 | JS_NUMBER = 2; 489 | } 490 | 491 | // Should this field be parsed lazily? Lazy applies only to message-type 492 | // fields. It means that when the outer message is initially parsed, the 493 | // inner message's contents will not be parsed but instead stored in encoded 494 | // form. The inner message will actually be parsed when it is first accessed. 495 | // 496 | // This is only a hint. Implementations are free to choose whether to use 497 | // eager or lazy parsing regardless of the value of this option. However, 498 | // setting this option true suggests that the protocol author believes that 499 | // using lazy parsing on this field is worth the additional bookkeeping 500 | // overhead typically needed to implement it. 501 | // 502 | // This option does not affect the public interface of any generated code; 503 | // all method signatures remain the same. Furthermore, thread-safety of the 504 | // interface is not affected by this option; const methods remain safe to 505 | // call from multiple threads concurrently, while non-const methods continue 506 | // to require exclusive access. 507 | // 508 | // 509 | // Note that implementations may choose not to check required fields within 510 | // a lazy sub-message. That is, calling IsInitialized() on the outer message 511 | // may return true even if the inner message has missing required fields. 512 | // This is necessary because otherwise the inner message would have to be 513 | // parsed in order to perform the check, defeating the purpose of lazy 514 | // parsing. An implementation which chooses not to check required fields 515 | // must be consistent about it. That is, for any particular sub-message, the 516 | // implementation must either *always* check its required fields, or *never* 517 | // check its required fields, regardless of whether or not the message has 518 | // been parsed. 519 | optional bool lazy = 5 [default=false]; 520 | 521 | // Is this field deprecated? 522 | // Depending on the target platform, this can emit Deprecated annotations 523 | // for accessors, or it will be completely ignored; in the very least, this 524 | // is a formalization for deprecating fields. 525 | optional bool deprecated = 3 [default=false]; 526 | 527 | // For Google-internal migration only. Do not use. 528 | optional bool weak = 10 [default=false]; 529 | 530 | 531 | // The parser stores options it doesn't recognize here. See above. 532 | repeated UninterpretedOption uninterpreted_option = 999; 533 | 534 | // Clients can define custom options in extensions of this message. See above. 535 | extensions 1000 to max; 536 | 537 | reserved 4; // removed jtype 538 | } 539 | 540 | message OneofOptions { 541 | // The parser stores options it doesn't recognize here. See above. 542 | repeated UninterpretedOption uninterpreted_option = 999; 543 | 544 | // Clients can define custom options in extensions of this message. See above. 545 | extensions 1000 to max; 546 | } 547 | 548 | message EnumOptions { 549 | 550 | // Set this option to true to allow mapping different tag names to the same 551 | // value. 552 | optional bool allow_alias = 2; 553 | 554 | // Is this enum deprecated? 555 | // Depending on the target platform, this can emit Deprecated annotations 556 | // for the enum, or it will be completely ignored; in the very least, this 557 | // is a formalization for deprecating enums. 558 | optional bool deprecated = 3 [default=false]; 559 | 560 | 561 | // The parser stores options it doesn't recognize here. See above. 562 | repeated UninterpretedOption uninterpreted_option = 999; 563 | 564 | // Clients can define custom options in extensions of this message. See above. 565 | extensions 1000 to max; 566 | } 567 | 568 | message EnumValueOptions { 569 | // Is this enum value deprecated? 570 | // Depending on the target platform, this can emit Deprecated annotations 571 | // for the enum value, or it will be completely ignored; in the very least, 572 | // this is a formalization for deprecating enum values. 573 | optional bool deprecated = 1 [default=false]; 574 | 575 | // The parser stores options it doesn't recognize here. See above. 576 | repeated UninterpretedOption uninterpreted_option = 999; 577 | 578 | // Clients can define custom options in extensions of this message. See above. 579 | extensions 1000 to max; 580 | } 581 | 582 | message ServiceOptions { 583 | 584 | // Note: Field numbers 1 through 32 are reserved for Google's internal RPC 585 | // framework. We apologize for hoarding these numbers to ourselves, but 586 | // we were already using them long before we decided to release Protocol 587 | // Buffers. 588 | 589 | // Is this service deprecated? 590 | // Depending on the target platform, this can emit Deprecated annotations 591 | // for the service, or it will be completely ignored; in the very least, 592 | // this is a formalization for deprecating services. 593 | optional bool deprecated = 33 [default=false]; 594 | 595 | // The parser stores options it doesn't recognize here. See above. 596 | repeated UninterpretedOption uninterpreted_option = 999; 597 | 598 | // Clients can define custom options in extensions of this message. See above. 599 | extensions 1000 to max; 600 | } 601 | 602 | message MethodOptions { 603 | 604 | // Note: Field numbers 1 through 32 are reserved for Google's internal RPC 605 | // framework. We apologize for hoarding these numbers to ourselves, but 606 | // we were already using them long before we decided to release Protocol 607 | // Buffers. 608 | 609 | // Is this method deprecated? 610 | // Depending on the target platform, this can emit Deprecated annotations 611 | // for the method, or it will be completely ignored; in the very least, 612 | // this is a formalization for deprecating methods. 613 | optional bool deprecated = 33 [default=false]; 614 | 615 | // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, 616 | // or neither? HTTP based RPC implementation may choose GET verb for safe 617 | // methods, and PUT verb for idempotent methods instead of the default POST. 618 | enum IdempotencyLevel { 619 | IDEMPOTENCY_UNKNOWN = 0; 620 | NO_SIDE_EFFECTS = 1; // implies idempotent 621 | IDEMPOTENT = 2; // idempotent, but may have side effects 622 | } 623 | optional IdempotencyLevel idempotency_level = 624 | 34 [default=IDEMPOTENCY_UNKNOWN]; 625 | 626 | // The parser stores options it doesn't recognize here. See above. 627 | repeated UninterpretedOption uninterpreted_option = 999; 628 | 629 | // Clients can define custom options in extensions of this message. See above. 630 | extensions 1000 to max; 631 | } 632 | 633 | 634 | // A message representing a option the parser does not recognize. This only 635 | // appears in options protos created by the compiler::Parser class. 636 | // DescriptorPool resolves these when building Descriptor objects. Therefore, 637 | // options protos in descriptor objects (e.g. returned by Descriptor::options(), 638 | // or produced by Descriptor::CopyTo()) will never have UninterpretedOptions 639 | // in them. 640 | message UninterpretedOption { 641 | // The name of the uninterpreted option. Each string represents a segment in 642 | // a dot-separated name. is_extension is true iff a segment represents an 643 | // extension (denoted with parentheses in options specs in .proto files). 644 | // E.g.,{ [[]"foo", false], [[]"bar.baz", true], [[]"qux", false] } represents 645 | // "foo.(bar.baz).qux". 646 | message NamePart { 647 | required string name_part = 1; 648 | required bool is_extension = 2; 649 | } 650 | repeated NamePart name = 2; 651 | 652 | // The value of the uninterpreted option, in whatever type the tokenizer 653 | // identified it as during parsing. Exactly one of these should be set. 654 | optional string identifier_value = 3; 655 | optional uint64 positive_int_value = 4; 656 | optional int64 negative_int_value = 5; 657 | optional double double_value = 6; 658 | optional bytes string_value = 7; 659 | optional string aggregate_value = 8; 660 | } 661 | 662 | // =================================================================== 663 | // Optional source code info 664 | 665 | // Encapsulates information about the original source file from which a 666 | // FileDescriptorProto was generated. 667 | message SourceCodeInfo { 668 | // A Location identifies a piece of source code in a .proto file which 669 | // corresponds to a particular definition. This information is intended 670 | // to be useful to IDEs, code indexers, documentation generators, and similar 671 | // tools. 672 | // 673 | // For example, say we have a file like: 674 | // message Foo { 675 | // optional string foo = 1; 676 | // } 677 | // Let's look at just the field definition: 678 | // optional string foo = 1; 679 | // ^ ^^ ^^ ^ ^^^ 680 | // a bc de f ghi 681 | // We have the following locations: 682 | // span path represents 683 | // [[]a,i) [[] 4, 0, 2, 0 ] The whole field definition. 684 | // [[]a,b) [[] 4, 0, 2, 0, 4 ] The label (optional). 685 | // [[]c,d) [[] 4, 0, 2, 0, 5 ] The type (string). 686 | // [[]e,f) [[] 4, 0, 2, 0, 1 ] The name (foo). 687 | // [[]g,h) [[] 4, 0, 2, 0, 3 ] The number (1). 688 | // 689 | // Notes: 690 | // - A location may refer to a repeated field itself (i.e. not to any 691 | // particular index within it). This is used whenever a set of elements are 692 | // logically enclosed in a single code segment. For example, an entire 693 | // extend block (possibly containing multiple extension definitions) will 694 | // have an outer location whose path refers to the "extensions" repeated 695 | // field without an index. 696 | // - Multiple locations may have the same path. This happens when a single 697 | // logical declaration is spread out across multiple places. The most 698 | // obvious example is the "extend" block again -- there may be multiple 699 | // extend blocks in the same scope, each of which will have the same path. 700 | // - A location's span is not always a subset of its parent's span. For 701 | // example, the "extendee" of an extension declaration appears at the 702 | // beginning of the "extend" block and is shared by all extensions within 703 | // the block. 704 | // - Just because a location's span is a subset of some other location's span 705 | // does not mean that it is a descendent. For example, a "group" defines 706 | // both a type and a field in a single declaration. Thus, the locations 707 | // corresponding to the type and field and their components will overlap. 708 | // - Code which tries to interpret locations should probably be designed to 709 | // ignore those that it doesn't understand, as more types of locations could 710 | // be recorded in the future. 711 | repeated Location location = 1; 712 | message Location { 713 | // Identifies which part of the FileDescriptorProto was defined at this 714 | // location. 715 | // 716 | // Each element is a field number or an index. They form a path from 717 | // the root FileDescriptorProto to the place where the definition. For 718 | // example, this path: 719 | // [[] 4, 3, 2, 7, 1 ] 720 | // refers to: 721 | // file.message_type(3) // 4, 3 722 | // .field(7) // 2, 7 723 | // .name() // 1 724 | // This is because FileDescriptorProto.message_type has field number 4: 725 | // repeated DescriptorProto message_type = 4; 726 | // and DescriptorProto.field has field number 2: 727 | // repeated FieldDescriptorProto field = 2; 728 | // and FieldDescriptorProto.name has field number 1: 729 | // optional string name = 1; 730 | // 731 | // Thus, the above path gives the location of a field name. If we removed 732 | // the last element: 733 | // [[] 4, 3, 2, 7 ] 734 | // this path refers to the whole field declaration (from the beginning 735 | // of the label to the terminating semicolon). 736 | repeated int32 path = 1 [packed=true]; 737 | 738 | // Always has exactly three or four elements: start line, start column, 739 | // end line (optional, otherwise assumed same as start line), end column. 740 | // These are packed into a single field for efficiency. Note that line 741 | // and column numbers are zero-based -- typically you will want to add 742 | // 1 to each before displaying to a user. 743 | repeated int32 span = 2 [packed=true]; 744 | 745 | // If this SourceCodeInfo represents a complete declaration, these are any 746 | // comments appearing before and after the declaration which appear to be 747 | // attached to the declaration. 748 | // 749 | // A series of line comments appearing on consecutive lines, with no other 750 | // tokens appearing on those lines, will be treated as a single comment. 751 | // 752 | // leading_detached_comments will keep paragraphs of comments that appear 753 | // before (but not connected to) the current element. Each paragraph, 754 | // separated by empty lines, will be one comment element in the repeated 755 | // field. 756 | // 757 | // Only the comment content is provided; comment markers (e.g. //) are 758 | // stripped out. For block comments, leading whitespace and an asterisk 759 | // will be stripped from the beginning of each line other than the first. 760 | // Newlines are included in the output. 761 | // 762 | // Examples: 763 | // 764 | // optional int32 foo = 1; // Comment attached to foo. 765 | // // Comment attached to bar. 766 | // optional int32 bar = 2; 767 | // 768 | // optional string baz = 3; 769 | // // Comment attached to baz. 770 | // // Another line attached to baz. 771 | // 772 | // // Comment attached to qux. 773 | // // 774 | // // Another line attached to qux. 775 | // optional double qux = 4; 776 | // 777 | // // Detached comment for corge. This is not leading or trailing comments 778 | // // to qux or corge because there are blank lines separating it from 779 | // // both. 780 | // 781 | // // Detached comment for corge paragraph 2. 782 | // 783 | // optional string corge = 5; 784 | // /* Block comment attached 785 | // * to corge. Leading asterisks 786 | // * will be removed. */ 787 | // /* Block comment attached to 788 | // * grault. */ 789 | // optional int32 grault = 6; 790 | // 791 | // // ignored detached comments. 792 | optional string leading_comments = 3; 793 | optional string trailing_comments = 4; 794 | repeated string leading_detached_comments = 6; 795 | } 796 | } 797 | 798 | // Describes the relationship between generated code and its original source 799 | // file. A GeneratedCodeInfo message is associated with only one generated 800 | // source file, but may contain references to different source .proto files. 801 | message GeneratedCodeInfo { 802 | // An Annotation connects some span of text in generated code to an element 803 | // of its generating .proto file. 804 | repeated Annotation annotation = 1; 805 | message Annotation { 806 | // Identifies the element in the original source .proto file. This field 807 | // is formatted the same as SourceCodeInfo.Location.path. 808 | repeated int32 path = 1 [packed=true]; 809 | 810 | // Identifies the filesystem path to the original source .proto. 811 | optional string source_file = 2; 812 | 813 | // Identifies the starting offset in bytes in the generated code 814 | // that relates to the identified object. 815 | optional int32 begin = 3; 816 | 817 | // Identifies the ending offset in bytes in the generated code that 818 | // relates to the identified offset. The end offset should be one past 819 | // the last relevant byte (so the length of the text = end - begin). 820 | optional int32 end = 4; 821 | } 822 | } 823 | 824 | // Protocol Buffers - Google's data interchange format 825 | // Copyright 2008 Google Inc. All rights reserved. 826 | // https://developers.google.com/protocol-buffers/ 827 | // 828 | // Redistribution and use in source and binary forms, with or without 829 | // modification, are permitted provided that the following conditions are 830 | // met: 831 | // 832 | // * Redistributions of source code must retain the above copyright 833 | // notice, this list of conditions and the following disclaimer. 834 | // * Redistributions in binary form must reproduce the above 835 | // copyright notice, this list of conditions and the following disclaimer 836 | // in the documentation and/or other materials provided with the 837 | // distribution. 838 | // * Neither the name of Google Inc. nor the names of its 839 | // contributors may be used to endorse or promote products derived from 840 | // this software without specific prior written permission. 841 | // 842 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 843 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 844 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 845 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 846 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 847 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 848 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 849 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 850 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 851 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 852 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 853 | 854 | // Author: kenton@google.com (Kenton Varda) 855 | // 856 | // WARNING: The plugin interface is currently EXPERIMENTAL and is subject to 857 | // change. 858 | // 859 | // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is 860 | // just a program that reads a CodeGeneratorRequest from stdin and writes a 861 | // CodeGeneratorResponse to stdout. 862 | // 863 | // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead 864 | // of dealing with the raw protocol defined here. 865 | // 866 | // A plugin executable needs only to be placed somewhere in the path. The 867 | // plugin should be named "protoc-gen-$NAME", and will then be used when the 868 | // flag "--${NAME}_out" is passed to protoc. 869 | 870 | //syntax = "proto2"; 871 | //package google.protobuf.compiler; 872 | //option java_package = "com.google.protobuf.compiler"; 873 | //option java_outer_classname = "PluginProtos"; 874 | 875 | //option go_package = "plugin_go"; 876 | 877 | //import "google/protobuf/descriptor.proto"; 878 | 879 | // The version number of protocol compiler. 880 | message Version { 881 | optional int32 major = 1; 882 | optional int32 minor = 2; 883 | optional int32 patch = 3; 884 | // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should 885 | // be empty for mainline stable releases. 886 | optional string suffix = 4; 887 | } 888 | 889 | // An encoded CodeGeneratorRequest is written to the plugin's stdin. 890 | message CodeGeneratorRequest { 891 | // The .proto files that were explicitly listed on the command-line. The 892 | // code generator should generate code only for these files. Each file's 893 | // descriptor will be included in proto_file, below. 894 | repeated string file_to_generate = 1; 895 | 896 | // The generator parameter passed on the command-line. 897 | optional string parameter = 2; 898 | 899 | // FileDescriptorProtos for all files in files_to_generate and everything 900 | // they import. The files will appear in topological order, so each file 901 | // appears before any file that imports it. 902 | // 903 | // protoc guarantees that all proto_files will be written after 904 | // the fields above, even though this is not technically guaranteed by the 905 | // protobuf wire format. This theoretically could allow a plugin to stream 906 | // in the FileDescriptorProtos and handle them one by one rather than read 907 | // the entire set into memory at once. However, as of this writing, this 908 | // is not similarly optimized on protoc's end -- it will store all fields in 909 | // memory at once before sending them to the plugin. 910 | repeated FileDescriptorProto proto_file = 15; 911 | 912 | // The version number of protocol compiler. 913 | optional Version compiler_version = 3; 914 | } 915 | 916 | // The plugin writes an encoded CodeGeneratorResponse to stdout. 917 | message CodeGeneratorResponse { 918 | // Error message. If non-empty, code generation failed. The plugin process 919 | // should exit with status code zero even if it reports an error in this way. 920 | // 921 | // This should be used to indicate errors in .proto files which prevent the 922 | // code generator from generating correct code. Errors which indicate a 923 | // problem in protoc itself -- such as the input CodeGeneratorRequest being 924 | // unparseable -- should be reported by writing a message to stderr and 925 | // exiting with a non-zero status code. 926 | optional string error = 1; 927 | 928 | // Represents a single generated file. 929 | message File { 930 | // The file name, relative to the output directory. The name must not 931 | // contain "." or ".." components and must be relative, not be absolute (so, 932 | // the file cannot lie outside the output directory). "/" must be used as 933 | // the path separator, not "\". 934 | // 935 | // If the name is omitted, the content will be appended to the previous 936 | // file. This allows the generator to break large files into small chunks, 937 | // and allows the generated text to be streamed back to protoc so that large 938 | // files need not reside completely in memory at one time. Note that as of 939 | // this writing protoc does not optimize for this -- it will read the entire 940 | // CodeGeneratorResponse before writing files to disk. 941 | optional string name = 1; 942 | 943 | // If non-empty, indicates that the named file should already exist, and the 944 | // content here is to be inserted into that file at a defined insertion 945 | // point. This feature allows a code generator to extend the output 946 | // produced by another code generator. The original generator may provide 947 | // insertion points by placing special annotations in the file that look 948 | // like: 949 | // @@protoc_insertion_point(NAME) 950 | // The annotation can have arbitrary text before and after it on the line, 951 | // which allows it to be placed in a comment. NAME should be replaced with 952 | // an identifier naming the point -- this is what other generators will use 953 | // as the insertion_point. Code inserted at this point will be placed 954 | // immediately above the line containing the insertion point (thus multiple 955 | // insertions to the same point will come out in the order they were added). 956 | // The double-@ is intended to make it unlikely that the generated code 957 | // could contain things that look like insertion points by accident. 958 | // 959 | // For example, the C++ code generator places the following line in the 960 | // .pb.h files that it generates: 961 | // // @@protoc_insertion_point(namespace_scope) 962 | // This line appears within the scope of the file's package namespace, but 963 | // outside of any particular class. Another plugin can then specify the 964 | // insertion_point "namespace_scope" to generate additional classes or 965 | // other declarations that should be placed in this scope. 966 | // 967 | // Note that if the line containing the insertion point begins with 968 | // whitespace, the same whitespace will be added to every line of the 969 | // inserted text. This is useful for languages like Python, where 970 | // indentation matters. In these languages, the insertion point comment 971 | // should be indented the same amount as any inserted code will need to be 972 | // in order to work correctly in that context. 973 | // 974 | // The code generator that generates the initial file and the one which 975 | // inserts into it must both run as part of a single invocation of protoc. 976 | // Code generators are executed in the order in which they appear on the 977 | // command line. 978 | // 979 | // If |insertion_point| is present, |name| must also be present. 980 | optional string insertion_point = 2; 981 | 982 | // The file contents. 983 | optional string content = 15; 984 | } 985 | repeated File file = 15; 986 | } 987 | -------------------------------------------------------------------------------- /pb-plugin/misc/messages_unnested.proto: -------------------------------------------------------------------------------- 1 | // no nested types 2 | // no extension ranges 3 | // no reserved ranges 4 | 5 | syntax = "proto2"; 6 | 7 | package google.protobuf; 8 | option go_package = "descriptor"; 9 | option java_package = "com.google.protobuf"; 10 | option java_outer_classname = "DescriptorProtos"; 11 | option csharp_namespace = "Google.Protobuf.Reflection"; 12 | option objc_class_prefix = "GPB"; 13 | 14 | option optimize_for = SPEED; 15 | 16 | message FileDescriptorSet { 17 | repeated FileDescriptorProto file = 1; 18 | } 19 | 20 | message FileDescriptorProto { 21 | optional string name = 1; 22 | optional string package = 2; 23 | 24 | repeated string dependency = 3; 25 | 26 | repeated int32 public_dependency = 10; 27 | 28 | repeated int32 weak_dependency = 11; 29 | 30 | repeated DescriptorProto message_type = 4; 31 | repeated EnumDescriptorProto enum_type = 5; 32 | repeated ServiceDescriptorProto service = 6; 33 | repeated FieldDescriptorProto extension = 7; 34 | 35 | optional FileOptions options = 8; 36 | 37 | optional SourceCodeInfo source_code_info = 9; 38 | 39 | optional string syntax = 12; 40 | } 41 | 42 | message ExtensionRange { 43 | optional int32 start = 1; 44 | optional int32 end = 2; 45 | } 46 | 47 | message ReservedRange { 48 | optional int32 start = 1; 49 | optional int32 end = 2; 50 | } 51 | 52 | message DescriptorProto { 53 | optional string name = 1; 54 | 55 | repeated FieldDescriptorProto field = 2; 56 | repeated FieldDescriptorProto extension = 6; 57 | 58 | repeated DescriptorProto nested_type = 3; 59 | repeated EnumDescriptorProto enum_type = 4; 60 | 61 | repeated ExtensionRange extension_range = 5; 62 | 63 | repeated OneofDescriptorProto oneof_decl = 8; 64 | 65 | optional MessageOptions options = 7; 66 | 67 | repeated ReservedRange reserved_range = 9; 68 | 69 | repeated string reserved_name = 10; 70 | } 71 | 72 | enum Label { 73 | 74 | LABEL_OPTIONAL = 1; 75 | LABEL_REQUIRED = 2; 76 | LABEL_REPEATED = 3; 77 | }; 78 | 79 | enum Type { 80 | 81 | TYPE_DOUBLE = 1; 82 | TYPE_FLOAT = 2; 83 | 84 | TYPE_INT64 = 3; 85 | TYPE_UINT64 = 4; 86 | 87 | TYPE_INT32 = 5; 88 | TYPE_FIXED64 = 6; 89 | TYPE_FIXED32 = 7; 90 | TYPE_BOOL = 8; 91 | TYPE_STRING = 9; 92 | TYPE_GROUP = 10; 93 | TYPE_MESSAGE = 11; 94 | 95 | TYPE_BYTES = 12; 96 | TYPE_UINT32 = 13; 97 | TYPE_ENUM = 14; 98 | TYPE_SFIXED32 = 15; 99 | TYPE_SFIXED64 = 16; 100 | TYPE_SINT32 = 17; 101 | TYPE_SINT64 = 18; 102 | }; 103 | 104 | message FieldDescriptorProto { 105 | 106 | 107 | optional string name = 1; 108 | optional int32 number = 3; 109 | optional Label label = 4; 110 | 111 | optional Type type = 5; 112 | 113 | optional string type_name = 6; 114 | 115 | optional string extendee = 2; 116 | 117 | optional string default_value = 7; 118 | 119 | optional int32 oneof_index = 9; 120 | 121 | optional string json_name = 10; 122 | 123 | optional FieldOptions options = 8; 124 | } 125 | 126 | message OneofDescriptorProto { 127 | optional string name = 1; 128 | optional OneofOptions options = 2; 129 | } 130 | 131 | message EnumDescriptorProto { 132 | optional string name = 1; 133 | 134 | repeated EnumValueDescriptorProto value = 2; 135 | 136 | optional EnumOptions options = 3; 137 | } 138 | 139 | message EnumValueDescriptorProto { 140 | optional string name = 1; 141 | optional int32 number = 2; 142 | 143 | optional EnumValueOptions options = 3; 144 | } 145 | 146 | message ServiceDescriptorProto { 147 | optional string name = 1; 148 | repeated MethodDescriptorProto method = 2; 149 | 150 | optional ServiceOptions options = 3; 151 | } 152 | 153 | message MethodDescriptorProto { 154 | optional string name = 1; 155 | 156 | optional string input_type = 2; 157 | optional string output_type = 3; 158 | 159 | optional MethodOptions options = 4; 160 | 161 | optional bool client_streaming = 5 [default=false]; 162 | 163 | optional bool server_streaming = 6 [default=false]; 164 | } 165 | 166 | enum OptimizeMode { 167 | SPEED = 1; 168 | 169 | CODE_SIZE = 2; 170 | LITE_RUNTIME = 3; 171 | } 172 | 173 | message FileOptions { 174 | 175 | optional string java_package = 1; 176 | 177 | optional string java_outer_classname = 8; 178 | 179 | optional bool java_multiple_files = 10 [default=false]; 180 | 181 | optional bool java_generate_equals_and_hash = 20 [deprecated=true]; 182 | 183 | optional bool java_string_check_utf8 = 27 [default=false]; 184 | 185 | optional OptimizeMode optimize_for = 9 [default=SPEED]; 186 | 187 | optional string go_package = 11; 188 | 189 | optional bool cc_generic_services = 16 [default=false]; 190 | optional bool java_generic_services = 17 [default=false]; 191 | optional bool py_generic_services = 18 [default=false]; 192 | 193 | optional bool deprecated = 23 [default=false]; 194 | 195 | optional bool cc_enable_arenas = 31 [default=false]; 196 | 197 | optional string objc_class_prefix = 36; 198 | 199 | optional string csharp_namespace = 37; 200 | 201 | optional string swift_prefix = 39; 202 | 203 | repeated UninterpretedOption uninterpreted_option = 999; 204 | 205 | // extensions 1000 to max; 206 | 207 | // reserved 38; 208 | } 209 | 210 | message MessageOptions { 211 | 212 | optional bool message_set_wire_format = 1 [default=false]; 213 | 214 | optional bool no_standard_descriptor_accessor = 2 [default=false]; 215 | 216 | optional bool deprecated = 3 [default=false]; 217 | 218 | optional bool map_entry = 7; 219 | 220 | // reserved 8; 221 | 222 | repeated UninterpretedOption uninterpreted_option = 999; 223 | 224 | // extensions 1000 to max; 225 | } 226 | 227 | enum CType { 228 | 229 | STRING = 0; 230 | 231 | CORD = 1; 232 | 233 | STRING_PIECE = 2; 234 | } 235 | 236 | enum JSType { 237 | 238 | JS_NORMAL = 0; 239 | 240 | JS_STRING = 1; 241 | 242 | JS_NUMBER = 2; 243 | } 244 | 245 | message FieldOptions { 246 | 247 | optional CType ctype = 1 [default = STRING]; 248 | 249 | optional bool packed = 2; 250 | 251 | optional JSType jstype = 6 [default = JS_NORMAL]; 252 | 253 | optional bool lazy = 5 [default=false]; 254 | 255 | optional bool deprecated = 3 [default=false]; 256 | 257 | optional bool weak = 10 [default=false]; 258 | 259 | repeated UninterpretedOption uninterpreted_option = 999; 260 | 261 | // extensions 1000 to max; 262 | 263 | // reserved 4; 264 | } 265 | 266 | message OneofOptions { 267 | 268 | repeated UninterpretedOption uninterpreted_option = 999; 269 | 270 | // extensions 1000 to max; 271 | } 272 | 273 | message EnumOptions { 274 | 275 | optional bool allow_alias = 2; 276 | 277 | optional bool deprecated = 3 [default=false]; 278 | 279 | repeated UninterpretedOption uninterpreted_option = 999; 280 | 281 | // extensions 1000 to max; 282 | } 283 | 284 | message EnumValueOptions { 285 | 286 | optional bool deprecated = 1 [default=false]; 287 | 288 | repeated UninterpretedOption uninterpreted_option = 999; 289 | 290 | // extensions 1000 to max; 291 | } 292 | 293 | message ServiceOptions { 294 | 295 | optional bool deprecated = 33 [default=false]; 296 | 297 | repeated UninterpretedOption uninterpreted_option = 999; 298 | 299 | // extensions 1000 to max; 300 | } 301 | 302 | enum IdempotencyLevel { 303 | IDEMPOTENCY_UNKNOWN = 0; 304 | NO_SIDE_EFFECTS = 1; 305 | IDEMPOTENT = 2; 306 | } 307 | 308 | message MethodOptions { 309 | 310 | optional bool deprecated = 33 [default=false]; 311 | 312 | optional IdempotencyLevel idempotency_level = 313 | 34 [default=IDEMPOTENCY_UNKNOWN]; 314 | 315 | repeated UninterpretedOption uninterpreted_option = 999; 316 | 317 | // extensions 1000 to max; 318 | } 319 | 320 | message NamePart { 321 | required string name_part = 1; 322 | required bool is_extension = 2; 323 | } 324 | 325 | message UninterpretedOption { 326 | 327 | repeated NamePart name = 2; 328 | 329 | optional string identifier_value = 3; 330 | optional uint64 positive_int_value = 4; 331 | optional int64 negative_int_value = 5; 332 | optional double double_value = 6; 333 | optional bytes string_value = 7; 334 | optional string aggregate_value = 8; 335 | } 336 | 337 | message Location { 338 | 339 | repeated int32 path = 1 [packed=true]; 340 | 341 | repeated int32 span = 2 [packed=true]; 342 | 343 | optional string leading_comments = 3; 344 | optional string trailing_comments = 4; 345 | repeated string leading_detached_comments = 6; 346 | } 347 | 348 | message SourceCodeInfo { 349 | 350 | repeated Location location = 1; 351 | } 352 | 353 | message Annotation { 354 | 355 | repeated int32 path = 1 [packed=true]; 356 | 357 | optional string source_file = 2; 358 | 359 | optional int32 begin = 3; 360 | 361 | optional int32 end = 4; 362 | } 363 | 364 | message GeneratedCodeInfo { 365 | 366 | repeated Annotation annotation = 1; 367 | } 368 | 369 | message Version { 370 | optional int32 major = 1; 371 | optional int32 minor = 2; 372 | optional int32 patch = 3; 373 | 374 | optional string suffix = 4; 375 | } 376 | 377 | message CodeGeneratorRequest { 378 | 379 | repeated string file_to_generate = 1; 380 | 381 | optional string parameter = 2; 382 | 383 | repeated FileDescriptorProto proto_file = 15; 384 | 385 | optional Version compiler_version = 3; 386 | } 387 | 388 | message File { 389 | 390 | optional string name = 1; 391 | 392 | optional string insertion_point = 2; 393 | 394 | optional string content = 15; 395 | } 396 | 397 | message CodeGeneratorResponse { 398 | 399 | optional string error = 1; 400 | 401 | repeated File file = 15; 402 | } 403 | -------------------------------------------------------------------------------- /pb-plugin/src/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (libraries pb integers batteries faraday angstrom) 3 | (package pb-plugin) 4 | (name pb_plugin) 5 | (public_name pb-plugin)) 6 | -------------------------------------------------------------------------------- /pb-plugin/src/pb_plugin.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | module M = Protoc_messages 9 | module G = Protobuf_reified.Generate 10 | 11 | (** Error handling *) 12 | exception Error of string 13 | 14 | let failf fmt = Printf.kprintf (fun s -> raise (Error s)) fmt 15 | let warnf fmt = Printf.fprintf stderr ("Warning: " ^^ fmt ^^ "\n") 16 | let is_some = function Some _ -> true | _ -> false 17 | let iter_opt f = function Some x -> f x | None -> () 18 | let map_opt f = function Some x -> Some (f x) | None -> None 19 | 20 | let basename s = 21 | let last = pred (String.length s) in 22 | match String.rindex_from s last '.' with 23 | i -> String.sub s (succ i) (last - i) 24 | | exception Not_found -> s 25 | 26 | module Context : 27 | sig 28 | type t 29 | val create : package:string option -> t 30 | 31 | val enum : t -> string -> G.enum 32 | (** hash-consed enums *) 33 | 34 | val message : t -> string -> G.message 35 | (** hash-consed messages *) 36 | 37 | val write_ml : Format.formatter -> t -> unit 38 | (** write out the contents of the context as an OCaml module *) 39 | end = 40 | struct 41 | type t = { 42 | package: string option; 43 | enums: (string, G.enum) Hashtbl.t; 44 | messages: (string, G.message) Hashtbl.t; 45 | } 46 | let create ~package = 47 | { package; 48 | messages = Hashtbl.create 10; 49 | enums = Hashtbl.create 10 } 50 | 51 | let message {messages; _} s = 52 | let s = basename s in 53 | match Hashtbl.find messages s with 54 | | m -> m 55 | | exception Not_found -> 56 | let m = G.message s in (Hashtbl.add messages s m; m) 57 | 58 | let enum {enums; _} s = 59 | let s = basename s in 60 | match Hashtbl.find enums s with 61 | | e -> e 62 | | exception Not_found -> 63 | let e = G.enum s in (Hashtbl.add enums s e; e) 64 | 65 | let write_ml fmt { package=_ ; enums ; messages } = 66 | (* TODO: package? *) 67 | let as_list h = Hashtbl.fold (fun _ -> List.cons) h [] in 68 | G.write fmt (as_list messages) (as_list enums) 69 | end 70 | 71 | module Enum : 72 | sig 73 | val generate : Context.t -> M.EnumDescriptorProto.s -> unit 74 | end = 75 | struct 76 | module EVO = M.EnumValueOptions 77 | 78 | let handle_option { EVO.deprecated; uninterpreted_option=_ } = 79 | if deprecated 80 | then warnf "ignoring option deprecated"; 81 | 82 | module EV = M.EnumValueDescriptorProto 83 | 84 | let genenum_value e = function 85 | | { EV.name = None; _ } -> failf "nameless enum values are not supported" 86 | | { EV.number = None; _ } -> failf "numberless enum values are not supported" 87 | | { EV.name = Some name; number = Some number; options } -> 88 | iter_opt handle_option options; 89 | G.constant name e number 90 | 91 | module E = M.EnumDescriptorProto 92 | 93 | let handle_option { M.EnumOptions.allow_alias; deprecated; uninterpreted_option=_ } = 94 | begin 95 | if is_some allow_alias 96 | then warnf "ignoring option allow_alias"; 97 | 98 | if deprecated 99 | then warnf "ignoring option deprecated"; 100 | end 101 | 102 | let generate context = function 103 | | { E.name = None; _ } -> failf "nameless enum values are not supported" 104 | | { E.name = Some name; value; options } -> 105 | iter_opt handle_option options; 106 | let e = Context.enum context name in 107 | List.iter (genenum_value e) value 108 | end 109 | 110 | 111 | module Message : 112 | sig 113 | val generate : Context.t -> M.DescriptorProto.s -> unit 114 | end = 115 | struct 116 | let gentype context t name = 117 | match M.Type.T.of_value (Pb.constant_value t), name with 118 | | `TYPE_DOUBLE, _ -> G.double 119 | | `TYPE_FLOAT, _ -> G.float 120 | | `TYPE_INT64, _ -> G.int64 121 | | `TYPE_UINT64, _ -> G.uint64 122 | | `TYPE_INT32, _ -> G.int32 123 | | `TYPE_FIXED64, _ -> G.fixed64 124 | | `TYPE_FIXED32, _ -> G.fixed32 125 | | `TYPE_BOOL, _ -> G.bool 126 | | `TYPE_STRING, _ -> G.string 127 | | `TYPE_GROUP, _ -> failf "groups are not supported" 128 | | `TYPE_MESSAGE, None -> failf "message fields without names are not supported" 129 | | `TYPE_MESSAGE, Some m -> G.message_field (Context.message context m) 130 | | `TYPE_BYTES, _ -> G.bytes 131 | | `TYPE_UINT32, _ -> G.uint32 132 | | `TYPE_ENUM, None -> failf "enum fields without names are not supported" 133 | | `TYPE_ENUM, Some e -> G.enum_field (Context.enum context e) 134 | | `TYPE_SFIXED32, _ -> G.sfixed32 135 | | `TYPE_SFIXED64, _ -> G.sfixed64 136 | | `TYPE_SINT32, _ -> G.sint32 137 | | `TYPE_SINT64, _ -> G.sint64 138 | 139 | module FO = M.FieldOptions 140 | 141 | let handle_option { FO.ctype=_; FO.packed; FO.jstype=_; FO.lazy_; FO.deprecated; FO.weak; 142 | FO.uninterpreted_option=_ } = 143 | begin 144 | if is_some packed 145 | then warnf "ignoring option packed"; 146 | 147 | if is_some packed 148 | then warnf "ignoring option packed"; 149 | 150 | if lazy_ 151 | then warnf "ignoring option lazy"; 152 | 153 | if deprecated 154 | then warnf "ignoring option deprecated"; 155 | 156 | if weak 157 | then warnf "ignoring option weak"; 158 | end 159 | 160 | module F = M.FieldDescriptorProto 161 | 162 | let genfield context msg = function 163 | | { F.name = None; _ } -> failf "nameless message fields not supported" 164 | | { F.number = None; _ } -> failf "numberless message fields not supported" 165 | | { F.type_ = None; _ } -> failf "typeless message fields not supported" 166 | | { F.name = Some name; number = Some number; label; type_ = Some type_; type_name; 167 | extendee; default_value; oneof_index; options; json_name=_ } as _r -> 168 | iter_opt handle_option options; 169 | begin 170 | if is_some extendee 171 | then warnf "ignoring extendee"; 172 | 173 | if is_some oneof_index 174 | then warnf "ignoring oneof_index"; 175 | end; 176 | let labelf = match label with 177 | None -> G.required 178 | | Some l -> match M.Label.T.of_value (Pb.constant_value l) with 179 | | `LABEL_REQUIRED -> G.required 180 | | `LABEL_REPEATED -> G.repeated 181 | | `LABEL_OPTIONAL -> 182 | begin match default_value with 183 | | Some default -> G.optional ~default 184 | | None -> G.optional ?default:None 185 | end 186 | in 187 | let typ = gentype context type_ type_name in 188 | labelf name msg typ number 189 | 190 | module MO = M.MessageOptions 191 | 192 | let handle_options { MO.message_set_wire_format; 193 | no_standard_descriptor_accessor; 194 | deprecated: bool; 195 | map_entry; 196 | uninterpreted_option=_ } = 197 | begin 198 | if message_set_wire_format 199 | then warnf "ignoring option message_set_wire_format"; 200 | 201 | if no_standard_descriptor_accessor 202 | then warnf "ignoring option no_standard_descriptor_accessor"; 203 | 204 | if deprecated 205 | then warnf "ignoring option deprecated"; 206 | 207 | if is_some map_entry 208 | then warnf "ignoring option map_entry"; 209 | end 210 | 211 | module D = M.DescriptorProto 212 | 213 | let generate context = function 214 | | { D.name = None; _ } -> failf "nameless messages not supported" 215 | | { D.name = Some name; field; extension; nested_type; enum_type; 216 | extension_range; oneof_decl; options; reserved_range; reserved_name } -> 217 | begin 218 | List.iter (fun _ -> failf "extension entries unsupported") extension; 219 | List.iter (fun _ -> failf "nested types unsupported") nested_type; 220 | List.iter (fun _ -> failf "nested enums unsupported") enum_type; 221 | List.iter (fun _ -> failf "extension ranges unsupported") extension_range; 222 | List.iter (fun _ -> failf "oneof declarations unsupported") oneof_decl; 223 | List.iter (fun _ -> failf "reserved ranges unsupported") reserved_range; 224 | List.iter (fun _ -> failf "reserved names unsupported") reserved_name; 225 | 226 | iter_opt handle_options options; 227 | 228 | let msg = Context.message context name in 229 | List.iter (genfield context msg) field 230 | end 231 | end 232 | 233 | let generate1 _input_file 234 | { M.FileDescriptorProto.name; enum_type; message_type; service; package; 235 | dependency=_; (* ? *) 236 | public_dependency=_; (* ? *) 237 | weak_dependency=_; (* ? *) 238 | extension=_; (* ? *) 239 | options=_; (* ? *) 240 | source_code_info=_; (* Locations *) 241 | syntax=_ (* protobuf syntax version (e.g. proto3) *) } : 242 | M.File.T.m Pb.msg = 243 | begin 244 | let context = Context.create ~package in 245 | begin match service with [] -> () | _ :: _ -> failf "Services not yet supported" end; 246 | List.iter (Enum.generate context) enum_type; 247 | let () = List.iter (Message.generate context) message_type in 248 | let name = map_opt 249 | (fun c -> Filename.chop_extension c ^ ".ml") name in 250 | M.File.mk ?name 251 | ~content:(Format.asprintf "%a" Context.write_ml context) 252 | () 253 | end 254 | 255 | let generate files proto_files = 256 | match List.combine files proto_files with 257 | | [(file, proto_file)] -> [generate1 file proto_file] 258 | | [] -> failf "error: exactly one filename (not zero) must be specified" 259 | | _::_::_ -> failf "error: exactly one filename (not more) must be specified" 260 | | exception Invalid_argument _ -> 261 | failf "Mismatched lengths: %d files, %d proto_files" 262 | (List.length files) 263 | (List.length proto_files) 264 | 265 | let run msg = 266 | let { M.CodeGeneratorRequest.file_to_generate; 267 | proto_file; parameter=_; compiler_version=_ } = 268 | M.CodeGeneratorRequest.extract msg 269 | in 270 | match generate file_to_generate proto_file with 271 | | files -> 272 | M.CodeGeneratorResponse.mk ~file:files () 273 | | exception (Error s) -> 274 | M.CodeGeneratorResponse.mk ~file:[] ~error:s () 275 | 276 | 277 | let main () = 278 | let stdin_buf = BatIO.(read_all stdin) in 279 | match Angstrom.(parse_string ~consume:Prefix) (Pb.read M.CodeGeneratorRequest.T.t) stdin_buf with 280 | Result.Error e -> failf "Error parssing request: %s" e 281 | | Result.Ok msg -> print_string (Faraday.serialize_to_string 282 | (Pb.write (run msg))) 283 | 284 | let () = main () 285 | -------------------------------------------------------------------------------- /pb-plugin/src/protobuf_reified.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | let failf fmt = Printf.kprintf failwith fmt 9 | 10 | module type GEN = 11 | sig 12 | type message 13 | val message : string -> message 14 | 15 | type enum 16 | val enum : string -> enum 17 | val constant : string -> enum -> int32 -> unit 18 | 19 | type field 20 | val enum_field : enum -> field 21 | val message_field : message -> field 22 | val bool : field 23 | val int32 : field 24 | val sint32 : field 25 | val sfixed32 : field 26 | val int64 : field 27 | val sint64 : field 28 | val sfixed64 : field 29 | val uint32 : field 30 | val fixed32 : field 31 | val uint64 : field 32 | val fixed64 : field 33 | val double : field 34 | val float : field 35 | val string : field 36 | val bytes : field 37 | 38 | val required : string -> message -> field -> int32 -> unit 39 | val optional : ?default:string -> string -> message -> field -> int32 -> unit 40 | val repeated : string -> message -> field -> int32 -> unit 41 | end 42 | 43 | module Generate : sig 44 | include GEN 45 | val write : Format.formatter -> message list -> enum list -> unit 46 | end = 47 | struct 48 | [@@@ocaml.warning "-34"] 49 | let ocaml_keywords = [ 50 | "and"; "as"; "assert"; "asr"; "begin"; "class"; "constraint"; 51 | "do"; "done"; "downto"; "effect"; "else"; "end"; "exception"; 52 | "external"; "false"; "for"; "fun"; "function"; "functor"; "if"; 53 | "implicit"; "in"; "include"; "inherit"; "initializer"; "land"; 54 | "lazy"; "let"; "lor"; "lsl"; "lsr"; "lxor"; "macro"; "match"; 55 | "method"; "mod"; "module"; "mutable"; "new"; "object"; "of"; 56 | "open"; "or"; "private"; "rec"; "sig"; "struct"; "then"; "to"; 57 | "true"; "try"; "type"; "val"; "virtual"; "when"; "with"; "while" 58 | ] 59 | 60 | let safe_id id = if List.mem id ocaml_keywords then id ^ "_" else id 61 | 62 | type message = { 63 | message_name: string; 64 | mutable fields: message_field list; 65 | } 66 | and enum = { 67 | enum_name: string; 68 | mutable constants: (string * int32) list; 69 | } 70 | and field = 71 | Message of message 72 | | Enum of enum 73 | | Basic of basic 74 | and message_field = { 75 | field_name: string; 76 | field_number: int32; 77 | field_type: field; 78 | field_class: [`required | `optional of string option | `repeated] 79 | } 80 | and method_ = { 81 | method_name: string; 82 | input_type: string; 83 | output_type: string; 84 | } 85 | and basic = { 86 | basic_type: string; 87 | basic_constructor: string; 88 | } 89 | let basic typ c : field = Basic { basic_type=typ; basic_constructor=c } 90 | let message name : message = { message_name=name; fields = [] } 91 | and enum name : enum = { enum_name=name; constants = [] } 92 | and constant name enum value = enum.constants <- (name, value) :: enum.constants 93 | and enum_field e = Enum e 94 | and message_field m = Message m 95 | and bool = basic "bool" "bool" 96 | and int32 = basic "int32" "int32" 97 | and sint32 = basic "int32" "sint32" 98 | and sfixed32 = basic "int32" "sfixed32" 99 | and int64 = basic "int64" "int64" 100 | and sint64 = basic "int64" "sint64" 101 | and sfixed64 = basic "int64" "sfixed64" 102 | and uint32 = basic "uint32" "uint32" 103 | and fixed32 = basic "uint32" "fixed32" 104 | and uint64 = basic "uint64" "uint64" 105 | and fixed64 = basic "uint64" "fixed64" 106 | and double = basic "double" "double" 107 | and float = basic "float" "float" 108 | and string = basic "string" "string" 109 | and bytes = basic "Bytes.t" "bytes" 110 | and required field_name msg field_type field_number = 111 | msg.fields <- { field_name; field_type; field_number; field_class = `required } :: msg.fields 112 | and optional ?default field_name msg field_type field_number = 113 | msg.fields <- { field_name; field_type; field_number; field_class = `optional default } :: msg.fields 114 | and repeated field_name msg field_type field_number = 115 | msg.fields <- { field_name; field_type; field_number; field_class = `repeated } :: msg.fields 116 | 117 | let default_value typ fmt s = 118 | Format.fprintf fmt "%s" 119 | (match typ, s with 120 | | Message _m, _ -> failf "Default values of message type are not supported" 121 | | Enum e, n when List.mem_assoc n e.constants -> 122 | let module_name = String.capitalize_ascii e.enum_name in 123 | let constant_name = String.lowercase_ascii n in 124 | Printf.sprintf "%s__constants.%s" module_name constant_name 125 | | Enum e, n -> failf "%s is not a member of enum %s\n" n e.enum_name 126 | | Basic {basic_type="bool";_}, "false" -> "false" 127 | | Basic {basic_type="bool";_}, "true" -> "true" 128 | | Basic {basic_type=t;_}, v -> 129 | Printf.sprintf "(assert false) (* %s of type %s *)" v t) 130 | 131 | let write_class field_type fmt = function 132 | | `required -> Format.pp_print_string fmt "required" 133 | | `optional None -> Format.pp_print_string fmt "optional" 134 | | `optional (Some d) -> Format.fprintf fmt "optional@ ~default:%a" (default_value field_type) d 135 | | `repeated -> Format.pp_print_string fmt "repeated" 136 | 137 | let write_field ~qualify fmt = function 138 | | Message {message_name;_} when qualify -> 139 | Format.fprintf fmt "Types_.s_%s" (String.capitalize_ascii message_name) 140 | | Message {message_name;_} -> 141 | Format.fprintf fmt "s_%s" (String.capitalize_ascii message_name) 142 | | Enum {enum_name;_} when qualify -> 143 | Format.fprintf fmt "@[Types_.%s.e@ Pb.enum@]" (String.capitalize_ascii enum_name) 144 | | Enum {enum_name;_} -> 145 | Format.fprintf fmt "@[%s.e@ Pb.enum@]" (String.capitalize_ascii enum_name) 146 | | Basic s -> 147 | Format.fprintf fmt "%s" s.basic_type 148 | 149 | let max_length = List.fold_left (fun l s -> max (String.length s) l) 0 150 | 151 | let write_record_field_type ~qualify fmt (cls, typ) = match cls with 152 | | `required -> (write_field ~qualify) fmt typ 153 | | `optional None -> Format.fprintf fmt "@[%a@ option@]" (write_field ~qualify) typ 154 | | `optional (Some _) -> Format.fprintf fmt "@[%a@]" (write_field ~qualify) typ 155 | | `repeated -> Format.fprintf fmt "@[%a@ list@]" (write_field ~qualify) typ 156 | 157 | let write_record_rhs ~qualify fmt fields = 158 | let pr f = Format.fprintf fmt f in 159 | ListLabels.iter fields ~f:begin fun {field_name; field_type; field_class; _} -> 160 | pr "@ @ @[%s:@ %a@];@\n" 161 | (safe_id field_name) (write_record_field_type ~qualify) (field_class, field_type); 162 | end 163 | 164 | let write_message fmt { message_name; fields } = 165 | let pr f = Format.fprintf fmt f in 166 | let module_name = String.capitalize_ascii message_name in 167 | pr "@[module@ %s@ =@ struct@\n" module_name; 168 | pr "@ @[include Types_.Fields__%s@]@\n@\n" module_name; 169 | pr "@ @[type s = Types_.s_%s = {@\n%a}@]@\n" module_name (write_record_rhs ~qualify:true) fields; 170 | pr "@\n"; 171 | 172 | (* 173 | Sample target code: 174 | 175 | type s = { 176 | ctype: CType.T.e enum; 177 | packed: bool option; 178 | jstype: JSType.T.e enum; 179 | lazy_: bool; 180 | deprecated: bool; 181 | weak: bool; 182 | uninterpreted_option: UninterpretedOption.s list; 183 | } 184 | 185 | let extract msg = { 186 | ctype = from_opt ~default:CType.string (getf msg ctype); 187 | packed = getf msg packed; 188 | jstype = from_opt ~default:JSType.normal (getf msg jstype); 189 | lazy_ = from_opt ~default:false (getf msg lazy_); 190 | deprecated = from_opt ~default:false (getf msg deprecated); 191 | weak = from_opt ~default:false (getf msg weak); 192 | uninterpreted_option = List.map UninterpretedOption.extract (getf msg uninterpreted_option); 193 | } 194 | *) 195 | pr "@ @[let@ extract@ =@ Types_.extract_%s@]@\n@\n" module_name; 196 | 197 | (* 198 | Sample target code: 199 | 200 | let mk ?ctype:x0 ?packed:x1 ?jstype:x2 ?lazy_:x3 ?deprecated:x4 ?weak:x5 ~uninterpreted_option:x6 () = 201 | let msg = create T.t in 202 | setf msg ctype x0; 203 | setf msg packed x1; 204 | setf msg jstype x2; 205 | setf msg lazy_ x3; 206 | setf msg deprecated x4; 207 | setf msg weak x5; 208 | setf msg uninterpreted_option x6; 209 | msg 210 | *) 211 | let arg cls i name = 212 | match cls with 213 | | `required | `repeated -> 214 | pr "@[~%s:_local%d@]@ " (safe_id name) i 215 | | `optional _ -> 216 | pr "@[?%s:_local%d@]@ " (safe_id name) i 217 | (* TODO: we could use default arguments here: *) 218 | (* 219 | | `optional None -> 220 | pr "@[?%s:_local%d@]@ " (safe_id name) i 221 | | `optional (Some default) -> 222 | pr "@[?%s:(_local%d=%s)@]@ " (safe_id name) i default 223 | *) 224 | in 225 | 226 | pr "@ @[let@ @[mk@ "; 227 | ListLabels.iteri fields ~f:begin 228 | fun i {field_name; field_class; _} -> 229 | arg field_class i field_name 230 | end; 231 | pr "@ ()@ =@]@\n"; 232 | 233 | pr "@ let@ _msg@ =@ @[Pb.create@ T.t@]@ in@\n"; 234 | 235 | ListLabels.iteri fields ~f:begin 236 | fun i {field_name; field_number=_; field_type=_; field_class=_} -> 237 | pr "@ @[Pb.setf@ _msg@ %s@ _local%d@];@\n" (safe_id field_name) i 238 | end; 239 | pr "@ _msg@]@\n"; 240 | 241 | 242 | pr "end@]@\n@\n" 243 | 244 | (* Mutual recursion: what can depend on what? 245 | 246 | type declarations 'module T = ...' do not depend on anything 247 | 248 | field declarations 'let x = T.required ...' may depend on any type declaration and on enum constants 249 | 250 | s declarations may depend on any type declaration 251 | 252 | extract declarations may depend on 253 | any type declaration 254 | and any extract declaration 255 | and any s declaration 256 | and any field declaration 257 | 258 | mk declarations may depend on 259 | the type declaration 260 | and the field declaration for the type 261 | *) 262 | 263 | let write_forward_declarations fmt messages enums = 264 | let pr f = Format.fprintf fmt f in 265 | (* Pb.message bindings *) 266 | pr "module@ Types_@ =@ struct@\n@["; 267 | ListLabels.iter messages ~f:begin fun { message_name; _ } -> 268 | let module_name = String.capitalize_ascii message_name in 269 | pr "@ @[module@ %s@ =@ @[(val@ Pb.message@ %S)@]@]" module_name module_name; 270 | end; 271 | ListLabels.iter enums ~f:begin fun { enum_name; _ } -> 272 | let module_name = String.capitalize_ascii enum_name in 273 | pr "@ @[module@ %s@ =@ @[(val@ Pb.enum@ %S)@]@]" module_name module_name; 274 | end; 275 | ListLabels.iter enums ~f:begin fun { enum_name; constants } -> 276 | let module_name = String.capitalize_ascii enum_name in 277 | pr "@ @[module@ %s__constants@ =@ @[struct@\n" module_name; 278 | ListLabels.iter (List.rev constants) ~f:begin fun (name, value) -> 279 | pr "@[@ let@ %s@ = %s.constant@ %S@ %ldl@]@\n" 280 | (String.lowercase_ascii name) module_name name value; 281 | end; 282 | pr "end@]@]@\n"; 283 | end; 284 | 285 | (* name bindings *) 286 | let write_field_type fmt typ = 287 | match typ with 288 | Basic b -> Format.fprintf fmt "Pb.%s" b.basic_constructor 289 | | Message m -> Format.fprintf fmt "(Pb.msg@ %s.t)" 290 | (String.capitalize_ascii m.message_name) 291 | | Enum e -> Format.fprintf fmt "%s.t" 292 | (String.capitalize_ascii e.enum_name) 293 | in 294 | 295 | ListLabels.iter messages ~f:begin fun { message_name; fields; _ } -> 296 | let module_name = String.capitalize_ascii message_name in 297 | pr "@ @[module@ Fields__%s@ =@ struct@\n" message_name; 298 | pr "@ @[module@ T@ =@ %s@]@\n@\n" module_name; 299 | let max_field_name_length = max_length 300 | (List.map (fun {field_name;_} -> field_name) fields) in 301 | ListLabels.iter (List.rev fields) ~f:begin 302 | fun {field_name; field_number; field_type; field_class} -> 303 | pr "@ @[let@ %s%*s@ @[T.%a@ %a@ %S@ %ld@]@]@\n" 304 | (safe_id field_name) (2 + (max_field_name_length - String.length field_name)) "=" 305 | (write_class field_type) field_class 306 | write_field_type field_type 307 | field_name 308 | field_number 309 | ; 310 | end; 311 | pr "@\nend@\n@]@\n"; 312 | end; 313 | 314 | 315 | (* type s bindings *) 316 | ListLabels.iteri messages ~f:begin fun i { message_name; fields; _ } -> 317 | let module_name = String.capitalize_ascii message_name in 318 | pr "@ @[%s@ s_%s@ =@ {@\n" (if i = 0 then "type" else "and") module_name; 319 | write_record_rhs ~qualify:false fmt fields; 320 | pr "}@]"; 321 | end; 322 | 323 | (* extract bindings *) 324 | 325 | let field_expr cls typ name = 326 | match cls, typ with 327 | | `required, Message m -> 328 | pr "@[extract_%s@ (Pb.getf@ _msg@ %s)@]" 329 | (String.capitalize_ascii m.message_name) 330 | (safe_id name) 331 | | `required, _ -> 332 | pr "@[Pb.getf@ _msg@ %s@]" (safe_id name) 333 | | `repeated, Message m -> 334 | pr "@[List.map@ extract_%s@ @[(Pb.getf@ _msg@ %s)@]@]" 335 | (String.capitalize_ascii m.message_name) name 336 | | `repeated, Enum _ -> 337 | (* pr "@[List.map@ %s.extract@ @[(Pb.getf@ _msg@ %s)@]@]" e.enum_name name *) 338 | pr "@[(Pb.getf@ _msg@ %s)@]" name 339 | | `repeated, Basic _ -> 340 | pr "@[Pb.getf@ _msg@ %s@]" name 341 | | `optional None, Message m -> 342 | pr "@[opt_map extract_%s @[(Pb.getf@ _msg@ %s)@]@]" 343 | (String.capitalize_ascii m.message_name) name 344 | | `optional None, _ -> 345 | pr "@[Pb.getf@ _msg@ %s@]" name 346 | | `optional (Some default), _ -> 347 | pr "@[from_opt@ ~default:%a@ (Pb.getf@ _msg@ %s)@]" (default_value typ) default name 348 | in 349 | 350 | 351 | ListLabels.iteri messages ~f:begin fun i { message_name; fields; _ } -> 352 | let module_name = String.capitalize_ascii message_name in 353 | pr "@ @[%s@ extract_%s@ _msg@ =@ Fields__%s.(({@\n" 354 | (if i = 0 then "let rec" else "and") 355 | module_name 356 | module_name; 357 | ListLabels.iter (List.rev fields) ~f:begin 358 | fun {field_name; field_type; field_class; _} -> 359 | pr "@ @ @[%s@ =@ " (safe_id field_name); 360 | field_expr field_class field_type (safe_id field_name); 361 | pr "@];@\n"; 362 | end; 363 | pr "}@ : s_%s))@]" module_name; 364 | end; 365 | 366 | pr "@]@\nend@\n@\n" 367 | 368 | let write_prologue fmt = 369 | let pr f = Format.fprintf fmt f in 370 | begin 371 | pr "@[(* Generated by ocaml-pb-plugin *)@]@\n@\n"; 372 | pr "[@@@@@@ocaml.warning \"-30-33-39\"] (* record labels reused, unused rec and unused open *)@\n@\n"; 373 | pr "open Unsigned@\n"; 374 | pr "module type TRANSPORT = sig end@\n"; 375 | pr "type double = float@\n"; 376 | pr "let from_opt ~default = function Some v -> v | None -> default@\n"; 377 | pr "let opt_map f = function Some v -> Some (f v) | None -> None@\n"; 378 | pr "let rassoc y l = fst (List.find (fun (_,y') -> y = y') l)@\n"; 379 | pr "@\n"; 380 | end 381 | 382 | let write_enum fmt { enum_name; constants } = 383 | let pr f = Format.fprintf fmt f in 384 | let module_name = String.capitalize_ascii enum_name in 385 | begin 386 | pr "module@ %s@ =@ @[struct@\n" module_name; 387 | pr "@[module T = struct@\n@[@;@;"; 388 | 389 | pr "@[type@ e@ =@ Types_.%s.e@]@\n" module_name; 390 | 391 | pr "@[let@ __map@ =@ ["; 392 | ListLabels.iter (List.rev constants) ~f:begin fun (name, v) -> 393 | pr "@\n@ @[%ldl,@ `%s@];" v name; 394 | end; 395 | pr "@]@\n]@\n"; 396 | pr "@[let of_value x = List.assoc x __map@]@\n"; 397 | pr "@[let to_value x = rassoc x __map@]"; 398 | pr "@]@]@\nend@\n"; 399 | pr "@[include@ Types_.%s__constants@]@\n" module_name; 400 | pr "@]@\nend@\n@\n"; 401 | end 402 | 403 | let write fmt messages enums = 404 | write_prologue fmt; 405 | write_forward_declarations fmt messages enums; 406 | List.iter (write_message fmt) messages; 407 | List.iter (write_enum fmt) enums 408 | end 409 | -------------------------------------------------------------------------------- /pb-plugin/test/.gitignore: -------------------------------------------------------------------------------- 1 | *.serialized 2 | -------------------------------------------------------------------------------- /pb-plugin/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yallop/ocaml-pb/b6cbb5c57ef6dc00ccf67af00634a14b451af742/pb-plugin/test/__init__.py -------------------------------------------------------------------------------- /pb-plugin/test/comprehensive.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | enum Enum { 4 | one = 1; 5 | two = 2; 6 | } 7 | 8 | message Small { 9 | optional string small_s = 100; 10 | optional int64 small_i = 200; 11 | } 12 | 13 | message Comprehensive { 14 | repeated uint32 repeated_uint32 = 1; 15 | required int32 required_int32 = 2; 16 | required Small required_Small = 3; 17 | required double required_double = 4; 18 | optional sfixed32 optional_sfixed32 = 5; 19 | optional fixed32 optional_fixed32 = 6; 20 | repeated bytes repeated_bytes = 7; 21 | repeated bool repeated_bool = 8; 22 | repeated sfixed64 repeated_sfixed64 = 9; 23 | optional bool optional_bool = 10; 24 | required uint32 required_uint32 = 11; 25 | optional double optional_double = 12; 26 | required int64 required_int64 = 13; 27 | required uint64 required_uint64 = 14; 28 | required string required_string = 15; 29 | required bytes required_bytes = 16; 30 | optional bytes optional_bytes = 17; 31 | optional sint64 optional_sint64 = 18; 32 | repeated sint64 repeated_sint64 = 19; 33 | repeated fixed32 repeated_fixed32 = 20; 34 | optional Small optional_Small = 21; 35 | optional int32 optional_int32 = 22; 36 | optional fixed64 optional_fixed64 = 23; 37 | optional Enum optional_enum = 24; 38 | required float required_float = 25; 39 | optional sfixed64 optional_sfixed64 = 26; 40 | required sfixed32 required_sfixed32 = 27; 41 | required bool required_bool = 28; 42 | repeated fixed64 repeated_fixed64 = 29; 43 | optional sint32 optional_sint32 = 30; 44 | repeated int64 repeated_int64 = 31; 45 | required fixed64 required_fixed64 = 32; 46 | repeated Enum repeated_enum = 33; 47 | optional int64 optional_int64 = 34; 48 | repeated float repeated_float = 35; 49 | repeated sint32 repeated_sint32 = 36; 50 | repeated uint64 repeated_uint64 = 37; 51 | repeated Small repeated_Small = 38; 52 | repeated double repeated_double = 39; 53 | repeated string repeated_string = 40; 54 | required sfixed64 required_sfixed64 = 41; 55 | required sint64 required_sint64 = 42; 56 | optional string optional_string = 43; 57 | optional uint32 optional_uint32 = 44; 58 | repeated sfixed32 repeated_sfixed32 = 45; 59 | optional float optional_float = 46; 60 | optional uint64 optional_uint64 = 47; 61 | required Enum required_enum = 48; 62 | required sint32 required_sint32 = 49; 63 | required fixed32 required_fixed32 = 50; 64 | repeated int32 repeated_int32 = 51; 65 | } 66 | -------------------------------------------------------------------------------- /pb-plugin/test/dune: -------------------------------------------------------------------------------- 1 | (rule 2 | (targets comprehensive.ml) 3 | (deps comprehensive.proto) 4 | (action (run protoc --plugin=protoc-gen-custom=%{bin:pb-plugin} --custom_out . %{deps}))) 5 | 6 | (rule 7 | (targets comprehensive_pb2.py) 8 | (deps comprehensive.proto) 9 | (action (run protoc --python_out=./ %{deps}))) 10 | 11 | (rule 12 | (targets 13 | comprehensive.python.serialized 14 | small.python.serialized) 15 | (deps (glob_files *.py)) 16 | (action (run python3 ./test_gen.py))) 17 | 18 | (alias 19 | (name runtest) 20 | (deps 21 | (glob_files *.py) 22 | small.ocaml.serialized 23 | comprehensive.ocaml.serialized) 24 | (action (run python3 ./test_read.py))) 25 | 26 | (rule 27 | (deps comprehensive.python.serialized) 28 | (targets comprehensive.ocaml.serialized small.ocaml.serialized) 29 | (action (run ./test.exe))) 30 | 31 | (executable 32 | (name test) 33 | (libraries ounit2 angstrom batteries faraday pb integers)) 34 | -------------------------------------------------------------------------------- /pb-plugin/test/test.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | open OUnit2 9 | module C = Comprehensive 10 | 11 | let msg_to_string m = Faraday.serialize_to_string (Pb.write m) 12 | 13 | let read_from_string p s = 14 | match Angstrom.(parse_string ~consume:Prefix) p s with 15 | | Result.Error _ -> Printf.kprintf failwith "parse failure (%s)" s 16 | | Result.Ok v -> v 17 | 18 | let slurp filename = BatFile.with_file_in filename BatIO.read_all 19 | 20 | let spew s filename = BatFile.with_file_out filename @@ fun fd -> 21 | BatIO.write_string fd s 22 | 23 | 24 | let test_interoperability_small_read _ = 25 | let open C.Small in 26 | let small_string = slurp "small.python.serialized" in 27 | let small = extract 28 | (read_from_string (Pb.read C.Types_.Small.t) small_string) in 29 | begin 30 | assert (small.small_s = Some "abc"); 31 | assert (small.small_i = Some 17L); 32 | end 33 | 34 | 35 | let test_interoperability_comprehensive_read _ = 36 | let open C.Small in 37 | let open C.Comprehensive in 38 | let comprehensive_string = slurp "comprehensive.python.serialized" in 39 | let c = extract (read_from_string (Pb.read C.Types_.Comprehensive.t) 40 | comprehensive_string) in 41 | begin 42 | let u32 = Unsigned.UInt32.of_int 43 | and u64 = Unsigned.UInt64.of_int 44 | and bytes = Bytes.of_string in 45 | assert (c.repeated_uint32 = [u32 1; u32 2]); 46 | assert (c.required_int32 = 3_l); 47 | let s = c.required_Small in 48 | assert (s.small_s = Some "abc"); 49 | assert (s.small_i = Some 17L); 50 | assert (c.required_double = 4.1); 51 | assert (c.optional_sfixed32 = Some 5_l); 52 | assert (c.optional_fixed32 = Some (u32 6)); 53 | assert (c.repeated_bytes = [bytes "def"; bytes "gh"]); 54 | assert (c.repeated_bool = [false; true]); 55 | assert (c.repeated_sfixed64 = [7_L; 8_L; 9_L]); 56 | assert (c.optional_bool = Some true); 57 | assert (c.required_uint32 = u32 10); 58 | assert (c.optional_double = Some 11.2); 59 | assert (c.required_int64 = 12_L); 60 | assert (c.required_uint64 = u64 13); 61 | assert (c.required_string = "rstuvw"); 62 | assert (c.required_bytes = bytes "lmnopq"); 63 | assert (c.optional_bytes = Some (bytes "rstuv")); 64 | assert (c.optional_sint64 = Some 14_L); 65 | assert (c.repeated_sint64 = [-15_L; 16_L; 17_L]); 66 | assert (c.repeated_fixed32 = [u32 18; u32 19; u32 20; u32 21]); 67 | let () = match c.optional_Small with 68 | None -> assert false 69 | | Some s -> 70 | assert (s.small_s = Some "abc"); 71 | assert (s.small_i = Some 17L); 72 | in 73 | assert (c.optional_int32 = Some 22_l); 74 | assert (c.optional_fixed64 = Some (u64 23)); 75 | assert (c.optional_enum = Some C.Enum.one); 76 | assert (c.required_float = 24.5); 77 | assert (c.optional_sfixed64 = Some 25_L); 78 | assert (c.required_sfixed32 = 26_l); 79 | assert (c.required_bool = true); 80 | assert (c.repeated_fixed64 = [u64 27; u64 28; u64 29; u64 30; u64 31]); 81 | assert (c.optional_sint32 = Some 32_l); 82 | assert (c.repeated_int64 = [33_L; 34_L; 35_L; 36_L; 37_L; 38_L; 39_L]); 83 | assert (c.required_fixed64 = u64 40); 84 | assert (c.repeated_enum = [C.Enum.one; C.Enum.two]); 85 | assert (c.optional_int64 = Some 41_L); 86 | assert (c.repeated_float = [42.0]); 87 | assert (c.repeated_sint32 = [44_l; 45_l; 46_l; 47_l; 48_l; 49_l]); 88 | assert (c.repeated_uint64 = [u64 50; u64 51; u64 52; u64 53; u64 54; u64 55]); 89 | let () = match c.repeated_Small with 90 | [s1; s2] -> 91 | assert (s2.small_s = None); 92 | assert (s2.small_i = Some 100L); 93 | assert (s1.small_s = Some "abc"); 94 | assert (s1.small_i = Some 17L); 95 | | _ -> assert false 96 | in 97 | assert (c.repeated_double = [56.3; 57.4; 58.0; 59.1]); 98 | assert (c.repeated_string = ["w"; ""; "yz"]); 99 | assert (c.required_sfixed64 = 60_L); 100 | assert (c.required_sint64 = 61_L); 101 | assert (c.optional_string = Some "A3"); 102 | assert (c.optional_uint32 = Some (u32 62)); 103 | assert (c.repeated_sfixed32 = [63_l; 64_l; 65_l; 66_l; 67_l; 68_l]); 104 | assert (c.optional_float = Some 69.0); 105 | assert (c.optional_uint64 = Some (u64 70)); 106 | assert (c.required_enum = C.Enum.two); 107 | assert (c.required_sint32 = 71_l); 108 | assert (c.required_fixed32 = u32 72); 109 | assert (c.repeated_int32 = [73_l; 74_l; 75_l; 76_l; 77_l; 78_l]); 110 | end 111 | 112 | 113 | let test_interoperability_small_write _ = 114 | let small = C.Small.mk ~small_s:"abc" ~small_i:17_L () in 115 | spew (msg_to_string small) "small.ocaml.serialized" 116 | 117 | 118 | let test_interoperability_comprehensive_write _ = 119 | let u32 = Unsigned.UInt32.of_int 120 | and u64 = Unsigned.UInt64.of_int 121 | and bytes = Bytes.of_string in 122 | let s = C.Small.mk ~small_s:"abc" ~small_i:17_L () in 123 | let s2 = C.Small.mk ~small_i:100_L () in 124 | let c = C.Comprehensive.mk 125 | ~repeated_uint32:[u32 1; u32 2] 126 | ~required_int32:3_l 127 | ~required_Small:s 128 | ~required_double:4.1 129 | ~optional_sfixed32:5_l 130 | ~optional_fixed32:(u32 6) 131 | ~repeated_bytes:[bytes "def"; bytes "gh"] 132 | ~repeated_bool:[false; true] 133 | ~repeated_sfixed64:[7_L; 8_L; 9_L] 134 | ~optional_bool:true 135 | ~required_uint32:(u32 10) 136 | ~optional_double:11.2 137 | ~required_int64:12_L 138 | ~required_uint64:(u64 13) 139 | ~required_string:"rstuvw" 140 | ~required_bytes:(bytes "lmnopq") 141 | ~optional_bytes:(bytes "rstuv") 142 | ~optional_sint64:14_L 143 | ~repeated_sint64:[-15_L; 16_L; 17_L] 144 | ~repeated_fixed32:[u32 18; u32 19; u32 20; u32 21] 145 | ~optional_Small:s 146 | ~optional_int32:22_l 147 | ~optional_fixed64:(u64 23) 148 | ~optional_enum:C.Enum.one 149 | ~required_float:24.5 150 | ~optional_sfixed64:25_L 151 | ~required_sfixed32:26_l 152 | ~required_bool:true 153 | ~repeated_fixed64:[u64 27; u64 28; u64 29; u64 30; u64 31] 154 | ~optional_sint32:32_l 155 | ~repeated_int64:[33_L; 34_L; 35_L; 36_L; 37_L; 38_L; 100000000039_L] 156 | ~required_fixed64:(u64 40) 157 | ~repeated_enum:[C.Enum.one; C.Enum.two] 158 | ~optional_int64:41_L 159 | ~repeated_float:[42.0] 160 | ~repeated_sint32:[44_l; 45_l; 46_l; 47_l; 48_l; 49_l] 161 | ~repeated_uint64:[u64 50; u64 51; u64 52; u64 53; u64 54; u64 55] 162 | ~repeated_Small:[s; s2] 163 | ~repeated_double:[56.3; 57.4; 58.0; 59.1] 164 | ~repeated_string:["w"; ""; "yz"] 165 | ~required_sfixed64:60_L 166 | ~required_sint64:61_L 167 | ~optional_string:"A3" 168 | ~optional_uint32:(u32 62) 169 | ~repeated_sfixed32:[63_l; 64_l; 65_l; 66_l; 67_l; 68_l] 170 | ~optional_float:69.0 171 | ~optional_uint64:(u64 70) 172 | ~required_enum:C.Enum.two 173 | ~required_sint32:71_l 174 | ~required_fixed32:(u32 72) 175 | ~repeated_int32:[73_l; 74_l; 75_l; 76_l; 77_l; 78_l] 176 | () 177 | in 178 | spew (msg_to_string c) "comprehensive.ocaml.serialized" 179 | 180 | 181 | let suite = "Protobuf wire format tests" >::: 182 | ["test interoperability (small read)" 183 | >:: test_interoperability_small_read; 184 | 185 | "test interoperability (comprehensive read)" 186 | >:: test_interoperability_comprehensive_read; 187 | 188 | "test interoperability (small write)" 189 | >:: test_interoperability_small_write; 190 | 191 | "test interoperability (comprehensive write)" 192 | >:: test_interoperability_comprehensive_write; 193 | ] 194 | 195 | 196 | let _ = 197 | run_test_tt_main suite 198 | -------------------------------------------------------------------------------- /pb-plugin/test/test_gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import comprehensive_pb2 4 | test_pb2 = comprehensive_pb2 5 | 6 | e1 = test_pb2.Enum.Value('one') 7 | e2 = test_pb2.Enum.Value('two') 8 | 9 | s1 = test_pb2.Small() 10 | s1.small_s = 'abc' 11 | s1.small_i = 17 12 | 13 | s2 = test_pb2.Small() 14 | s2.small_i = 100 15 | 16 | c = test_pb2.Comprehensive() 17 | c.repeated_uint32.extend([1,2]) 18 | c.required_int32 = 3 19 | c.required_Small.CopyFrom(s1) 20 | c.required_double = 4.1 21 | c.optional_sfixed32 = 5 22 | c.optional_fixed32 = 6 23 | c.repeated_bytes.extend([b'def', b'gh']) 24 | c.repeated_bool.extend([False, True]) 25 | c.repeated_sfixed64.extend([7,8,9]) 26 | c.optional_bool = True 27 | c.required_uint32 = 10 28 | c.optional_double = 11.2 29 | c.required_int64 = 12 30 | c.required_uint64 = 13 31 | c.required_string = 'rstuvw' 32 | c.required_bytes = b'lmnopq' 33 | c.optional_bytes = b'rstuv' 34 | c.optional_sint64 = 14 35 | c.repeated_sint64.extend([-15,16,17]) 36 | c.repeated_fixed32.extend([18,19,20,21]) 37 | c.optional_Small.CopyFrom(s1) 38 | c.optional_int32 = 22 39 | c.optional_fixed64 = 23 40 | c.optional_enum = e1 41 | c.required_float = 24.5 42 | c.optional_sfixed64 = 25 43 | c.required_sfixed32 = 26 44 | c.required_bool = True 45 | c.repeated_fixed64.extend([27,28,29,30,31]) 46 | c.optional_sint32 = 32 47 | c.repeated_int64.extend([33,34,35,36,37,38,39]) 48 | c.required_fixed64 = 40 49 | c.repeated_enum.extend([e1, e2]) 50 | c.optional_int64 = 41 51 | c.repeated_float.extend([42.0]) 52 | c.repeated_sint32.extend([44, 45, 46, 47, 48, 49]) 53 | c.repeated_uint64.extend([50, 51, 52, 53, 54, 55]) 54 | c.repeated_Small.extend([s1, s2]) 55 | c.repeated_double.extend([56.3, 57.4, 58.0, 59.1]) 56 | c.repeated_string.extend(['w', '', 'yz']) 57 | c.required_sfixed64 = 60 58 | c.required_sint64 = 61 59 | c.optional_string = 'A3' 60 | c.optional_uint32 = 62 61 | c.repeated_sfixed32.extend([63, 64, 65, 66, 67, 68]) 62 | c.optional_float = 69.0 63 | c.optional_uint64 = 70 64 | c.required_enum = e2 65 | c.required_sint32 = 71 66 | c.required_fixed32 = 72 67 | c.repeated_int32.extend([73, 74, 75, 76, 77, 78]) 68 | 69 | def main(): 70 | print('generating comprehensive.serialized') 71 | with open('comprehensive.python.serialized', 'wb') as fd: 72 | fd.write(c.SerializeToString()) 73 | 74 | print('generating small.serialized') 75 | with open('small.python.serialized', 'wb') as fd: 76 | fd.write(s1.SerializeToString()) 77 | 78 | if __name__ == '__main__': 79 | main() 80 | -------------------------------------------------------------------------------- /pb-plugin/test/test_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import comprehensive_pb2 4 | test_pb2 = comprehensive_pb2 5 | 6 | def from_bytes(msg, filename): 7 | with open(filename, 'rb') as fd: 8 | return msg.FromString(fd.read()) 9 | 10 | def main(): 11 | small = from_bytes(test_pb2.Small, 'small.ocaml.serialized') 12 | assert small.small_s == 'abc' 13 | assert small.small_i == 17 14 | 15 | if True: return 16 | 17 | c = from_bytes(test_pb2.Comprehensive, 'comprehensive.ocaml.serialized') 18 | assert list(c.repeated_uint32) == [1,2] 19 | assert c.required_int32 == 3 20 | s1 = c.required_Small 21 | assert s1.small_s == 'abc' 22 | assert s1.small_i == 17 23 | assert c.required_double == 4.1 24 | assert c.optional_sfixed32 == 5 25 | assert c.optional_fixed32 == 6 26 | assert list(c.repeated_bytes) == ['def', 'gh'] 27 | assert list(c.repeated_bool) == [False, True] 28 | assert list(c.repeated_sfixed64) == [7,8,9] 29 | assert c.optional_bool == True 30 | assert c.required_uint32 == 10 31 | assert c.optional_double == 11.2 32 | assert c.required_int64 == 12 33 | assert c.required_uint64 == 13 34 | assert c.required_string == 'rstuvw' 35 | assert c.required_bytes == 'lmnopq' 36 | assert c.optional_bytes == 'rstuv' 37 | assert c.optional_sint64 == 14 38 | assert list(c.repeated_sint64) == [-15,16,17] 39 | assert list(c.repeated_fixed32) == [18,19,20,21] 40 | s1 = c.optional_Small 41 | assert s1.small_s == 'abc' 42 | assert s1.small_i == 17 43 | assert c.optional_int32 == 22 44 | assert c.optional_fixed64 == 23 45 | e1 = c.optional_enum 46 | assert e1 == test_pb2.Enum.Value('one') 47 | assert c.required_float == 24.5 48 | assert c.optional_sfixed64 == 25 49 | assert c.required_sfixed32 == 26 50 | assert c.required_bool == True 51 | assert list(c.repeated_fixed64) == [27,28,29,30,31] 52 | assert c.optional_sint32 == 32 53 | assert list(c.repeated_int64) == [33,34,35,36,37,38,100000000039] 54 | assert c.required_fixed64 == 40 55 | e2 = test_pb2.Enum.Value('two') 56 | assert list(c.repeated_enum) == [e1, e2] 57 | assert c.optional_int64 == 41 58 | assert list(c.repeated_float) == [42.0] 59 | assert list(c.repeated_sint32) == [44, 45, 46, 47, 48, 49] 60 | assert list(c.repeated_uint64) == [50, 51, 52, 53, 54, 55] 61 | [s1, s2] = c.repeated_Small 62 | assert s1.small_s == 'abc' 63 | assert s1.small_i == 17 64 | assert s2.small_s == '' 65 | assert s2.small_i == 100 66 | assert list(c.repeated_double) == [56.3, 57.4, 58.0, 59.1] 67 | assert list(c.repeated_string) == ['w', '', 'yz'] 68 | assert c.required_sfixed64 == 60 69 | assert c.required_sint64 == 61 70 | assert c.optional_string == 'A3' 71 | assert c.optional_uint32 == 62 72 | assert list(c.repeated_sfixed32) == [63, 64, 65, 66, 67, 68] 73 | assert c.optional_float == 69.0 74 | assert c.optional_uint64 == 70 75 | assert c.required_enum == e2 76 | assert c.required_sint32 == 71 77 | assert c.required_fixed32 == 72 78 | assert list(c.repeated_int32) == [73, 74, 75, 76, 77, 78] 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /pb.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Library for describing Protobuf messages" 4 | description: "Library for describing Protobuf messages" 5 | maintainer: [ 6 | "Jeremy Yallop " "Rudi Grinberg " 7 | ] 8 | authors: ["Jeremy Yallop "] 9 | license: "MIT" 10 | homepage: "https://github.com/yallop/ocaml-pb" 11 | bug-reports: "https://github.com/yallop/ocaml-pb/issues" 12 | depends: [ 13 | "ocaml" {>= "4.03"} 14 | "dune" {>= "1.11"} 15 | "integers" 16 | "angstrom" {>= "0.14.0"} 17 | "faraday" 18 | "ounit" {with-test & >= "2.0"} 19 | ] 20 | dev-repo: "git+https://github.com/yallop/ocaml-pb.git" 21 | build: [ 22 | ["dune" "subst"] {pinned} 23 | [ 24 | "dune" 25 | "build" 26 | "-p" 27 | name 28 | "-j" 29 | jobs 30 | "@install" 31 | "@doc" {with-doc} 32 | ] 33 | ] 34 | -------------------------------------------------------------------------------- /pb.opam.template: -------------------------------------------------------------------------------- 1 | build: [ 2 | ["dune" "subst"] {pinned} 3 | [ 4 | "dune" 5 | "build" 6 | "-p" 7 | name 8 | "-j" 9 | jobs 10 | "@install" 11 | "@doc" {with-doc} 12 | ] 13 | ] 14 | -------------------------------------------------------------------------------- /pb/src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name pb) 3 | (public_name pb) 4 | (synopsis "Protobuf encodings.") 5 | (flags :standard -w -9-27-32-33-37-50) 6 | (libraries integers angstrom faraday)) 7 | -------------------------------------------------------------------------------- /pb/src/pb.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | (* Protobuf wire types *) 9 | 10 | open Unsigned 11 | 12 | exception Parse_error 13 | 14 | module Wire_type = Wire_type 15 | 16 | type key = { 17 | field_number: uint64; 18 | wire_type: Wire_type.t 19 | } 20 | 21 | module KeyMap = MoreLabels.Map.Make(struct 22 | type t = key 23 | let compare a b = 24 | match UInt64.compare a.field_number b.field_number with 25 | | 0 -> Wire_type.compare a.wire_type b.wire_type 26 | | n -> n 27 | end) 28 | 29 | type 'a enum = Enum_constant of int32 30 | 31 | type _ field_type = 32 | | Fixed64 : uint64 field_type 33 | | Sfixed64 : int64 field_type 34 | | Double : float field_type 35 | | String : string field_type 36 | | Bytes : bytes field_type 37 | | Fixed32 : uint32 field_type 38 | | Sfixed32 : int32 field_type 39 | | Float : float field_type 40 | | Varint : uint64 field_type 41 | | Int32 : int32 field_type 42 | | Int64 : int64 field_type 43 | | Uint32 : uint32 field_type 44 | | Uint64 : uint64 field_type 45 | | Sint32 : int32 field_type 46 | | Sint64 : int64 field_type 47 | | Bool : bool field_type 48 | | Msg : 'm msgtype -> 'm msg field_type 49 | | Enum : (int32 * string) list ref -> 'e enum field_type 50 | and ('a, 'b) field_kind = 51 | Optional : {default:'a option} -> ('a, 'a option) field_kind 52 | | Repeated : {packed:bool} -> ('a, 'a list) field_kind 53 | | Required: ('a, 'a) field_kind 54 | and ('m,'a) field = Field : { 55 | key: key; 56 | field_type: 'a field_type; 57 | name: string; 58 | field_kind: ('a, 'b) field_kind 59 | } -> ('m, 'b) field 60 | and 'm boxed_field = Boxed_field : ('m, 'a) field -> 'm boxed_field 61 | and 'm msgtype = { 62 | msg_name: string; 63 | mutable fields: 'm boxed_field list; 64 | } 65 | and 'a msg = { mutable contents: string list KeyMap.t; } 66 | 67 | let one_two_seven = UInt64.of_int 127 68 | 69 | let write_varint : Faraday.t -> uint64 -> unit = 70 | fun f i -> 71 | let rec loop b = 72 | let byte = UInt64.to_int UInt64.Infix.(b land UInt64.of_int 0x7f) in 73 | if b > one_two_seven 74 | then begin 75 | Faraday.write_uint8 f (byte lor 128); 76 | loop UInt64.Infix.(b lsr 7); 77 | end 78 | else Faraday.write_uint8 f byte 79 | in loop i 80 | 81 | let fixed64 = Fixed64 82 | let sfixed64 = Sfixed64 83 | let double = Double 84 | let string = String (* UTF-8, but we don't decode it *) 85 | let bytes = Bytes 86 | let fixed32 = Fixed32 87 | let sfixed32 = Sfixed32 88 | let float = Float 89 | let int32 = Int32 90 | let int64 = Int64 91 | let uint32 = Uint32 92 | let uint64 = Uint64 93 | let sint32 = Sint32 94 | let sint64 = Sint64 95 | let bool = Bool 96 | let msg mt = Msg mt 97 | 98 | let write_int64 f v = write_varint f (UInt64.of_int64 v) 99 | 100 | let write_int32 f v = write_int64 f (Int64.of_int32 v) 101 | 102 | let write_fixed_int64 : Faraday.t -> int64 -> unit = 103 | fun f x -> Faraday.LE.write_uint64 f x 104 | 105 | let write_fixed_uint64 : Faraday.t -> uint64 -> unit = 106 | fun f x -> Faraday.LE.write_uint64 f (UInt64.to_int64 x) 107 | 108 | let write_bytes f b = 109 | write_int64 f (Int64.of_int (Bytes.length b)); 110 | Faraday.write_bytes f b 111 | 112 | let write_fixed_int32 : Faraday.t -> int32 -> unit = 113 | fun f x -> Faraday.LE.write_uint32 f x 114 | 115 | let write_fixed_uint32 : Faraday.t -> uint32 -> unit = 116 | fun f x -> Faraday.LE.write_uint32 f (UInt32.to_int32 x) 117 | 118 | (* Each key in the streamed message is a varint with the value 119 | (field_number << 3) | wire_type – in other words, the last three bits 120 | of the number store the wire type. *) 121 | let key_of_uint64 k = 122 | let open UInt64 in 123 | let wire_type = Infix.(Wire_type.of_int Infix.(to_int (k land of_int 0x7))) 124 | and field_number = Infix.(k lsr 3) in 125 | { field_number; wire_type } 126 | 127 | let uint64_of_key { field_number; wire_type } = 128 | let open UInt64 in 129 | Infix.((field_number lsl 3) lor of_int (Wire_type.to_int wire_type)) 130 | 131 | module type ENUM = sig 132 | type e 133 | val t : e enum field_type 134 | val constant : string -> int32 -> e enum 135 | end 136 | 137 | module Enum(X: sig val name : string end) : 138 | sig 139 | include ENUM 140 | val constants : (int32 * string) list ref 141 | end = 142 | struct 143 | type e = X 144 | let of_enum (Enum_constant c) = c 145 | let to_enum c = Enum_constant c 146 | let constants = ref [] 147 | let t = Enum constants 148 | let constant s c = 149 | let () = constants := (c, s) :: !constants in 150 | (to_enum c) 151 | end 152 | 153 | let enum s = (module Enum(struct let name = s end) : ENUM) 154 | 155 | let constant_value (Enum_constant i) = i 156 | 157 | module type MESSAGE = sig 158 | type m 159 | val t : m msgtype 160 | val optional : ?default:'a -> 'a field_type -> string -> int -> 161 | (m, 'a option) field 162 | val repeated : ?packed:bool -> 'a field_type -> string -> int -> 163 | (m, 'a list) field 164 | val required : 'a field_type -> string -> int -> 165 | (m, 'a) field 166 | end 167 | 168 | let rec write_field : type a.a field_type -> Faraday.t -> a -> unit = 169 | fun t f v -> match t with 170 | | Fixed64 -> write_fixed_uint64 f v 171 | | Sfixed64 -> write_fixed_int64 f v 172 | | Double -> write_fixed_int64 f (Int64.bits_of_float v) 173 | | String -> write_bytes f (Bytes.unsafe_of_string v) 174 | | Bytes -> write_bytes f v 175 | | Fixed32 -> write_fixed_uint32 f v 176 | | Sfixed32 -> write_fixed_int32 f v 177 | | Float -> write_fixed_int32 f (Int32.bits_of_float v) 178 | | Varint -> write_varint f v 179 | | Int32 -> write_int32 f v 180 | | Int64 -> write_int64 f v 181 | | Uint32 -> let v = UInt64.of_int64 (Int64.of_int32 (UInt32.to_int32 v)) in 182 | write_varint f v 183 | | Uint64 -> write_varint f v 184 | | Sint32 -> let n = Signed.Int32.(Infix.((v lsl 1) lxor (shift_right v 31))) in 185 | write_int32 f n 186 | | Sint64 -> let n = Signed.Int64.(Infix.((v lsl 1) lxor (shift_right v 63))) in 187 | write_int64 f n 188 | | Bool -> if v then write_varint f UInt64.one 189 | else write_varint f UInt64.zero 190 | | Msg mt -> 191 | let f' = Faraday.create 128 in 192 | let () = write_contents f' v.contents in 193 | let s = Faraday.serialize_to_string f' in 194 | let () = write_varint f (UInt64.of_int (String.length s)) in 195 | Faraday.write_string f s 196 | | Enum _ -> let Enum_constant c = v in write_int32 f c 197 | 198 | and write_contents : Faraday.t -> string list KeyMap.t -> unit = 199 | fun f contents -> 200 | KeyMap.iter contents ~f:(fun ~key:k ~data -> 201 | ListLabels.iter data ~f:(fun v -> 202 | write_varint f (uint64_of_key k); 203 | Faraday.write_string f v)) 204 | 205 | let string_of_field ft v = 206 | let fd = Faraday.create 128 in 207 | let () = write_field ft fd v in 208 | let s = Faraday.serialize_to_string fd in 209 | s 210 | 211 | let read_varint : uint64 Angstrom.t = 212 | let open Angstrom in 213 | let add c (acc : uint64) shift = 214 | let shft = 7 * shift in 215 | UInt64.(Infix.(acc + (of_int c lsl shft))) 216 | in 217 | let rec loop (acc : uint64) shift = 218 | any_char >>= fun c -> 219 | if Char.code c land 0x80 > 0 then 220 | loop (add (Char.code c land 0x7f) acc shift) (succ shift) 221 | else return (add (Char.code c) acc shift) 222 | in loop UInt64.zero 0 223 | 224 | let read_string : string Angstrom.t = 225 | Angstrom.(read_varint >>| UInt64.to_int >>= take) 226 | 227 | let read_key = 228 | Angstrom.(read_varint >>| key_of_uint64) 229 | 230 | let read_int32 = 231 | Angstrom.(read_varint >>| UInt64.to_int64 >>| Int64.to_int32) 232 | 233 | let read_int64 = 234 | Angstrom.(read_varint >>| UInt64.to_int64) 235 | 236 | let read_bytes = 237 | Angstrom.(read_string >>| Bytes.unsafe_of_string) 238 | 239 | let read_unknown : Wire_type.t -> string Angstrom.t = 240 | let open Angstrom in 241 | let open Wire_type in 242 | function 243 | | Varint -> 244 | read_varint >>= fun v -> 245 | return (string_of_field Varint v) 246 | | Sixty_four -> 247 | take 8 248 | | Length_delimited -> 249 | read_bytes >>| string_of_field bytes 250 | | Start_group -> failwith "Not supported: start_group" 251 | | End_group -> failwith "Not supported: end_group" 252 | | Thirty_two -> 253 | take 4 254 | 255 | let read_contents : string list KeyMap.t Angstrom.t = 256 | let add_one : string list KeyMap.t -> key -> string -> string list KeyMap.t = 257 | fun h k v -> 258 | match KeyMap.find k h with 259 | | exception Not_found -> KeyMap.add h ~key:k ~data:[v] 260 | | vs -> KeyMap.add h ~key:k ~data:(v :: vs) 261 | in 262 | let map_of_items items = 263 | List.fold_left (fun a (k, s) -> add_one a k s) items 264 | in 265 | let open Angstrom in 266 | let read_kv = 267 | read_key >>= fun k -> 268 | read_unknown k.wire_type >>= fun v -> 269 | return (k, v) 270 | in 271 | many read_kv >>| map_of_items KeyMap.empty 272 | 273 | let read_field : type a. a field_type -> a Angstrom.t = 274 | let open Angstrom in function 275 | | Fixed64 -> LE.any_int64 >>| UInt64.of_int64 276 | | Sfixed64 -> LE.any_int64 277 | | Double -> LE.any_int64 >>| Int64.float_of_bits 278 | | String -> read_string 279 | | Bytes -> read_bytes 280 | | Fixed32 -> LE.any_int32 >>| UInt32.of_int32 281 | | Sfixed32 -> LE.any_int32 282 | | Float -> LE.any_int32 >>| Int32.float_of_bits 283 | | Varint -> read_varint 284 | | Int32 -> read_int32 285 | | Int64 -> read_varint >>| UInt64.to_int64 286 | | Uint32 -> read_varint >>| UInt64.to_int64 287 | >>| Int64.to_int32 288 | >>| UInt32.of_int32 289 | | Uint64 -> read_varint 290 | | Sint32 -> read_int32 >>| fun n -> 291 | Signed.Int32.(Infix.((n lsr 1) lxor neg (n land 1l))) 292 | | Sint64 -> read_int64 >>| fun n -> 293 | Signed.Int64.(Infix.((n lsr 1) lxor neg (n land 1L))) 294 | | Bool -> read_varint >>| fun i -> 295 | not (i = UInt64.zero) 296 | | Msg mt -> read_string >>| fun s -> 297 | begin match Angstrom.(parse_string ~consume:Prefix) read_contents s with 298 | Result.Ok contents -> { contents } 299 | | Result.Error _ -> raise Parse_error 300 | end 301 | | Enum _ -> read_int32 >>| fun i -> 302 | Enum_constant i 303 | 304 | let wire_type : type a. a field_type -> Wire_type.t = 305 | let open Wire_type in function 306 | | Fixed64 -> Sixty_four 307 | | Sfixed64 -> Sixty_four 308 | | Double -> Sixty_four 309 | 310 | | String -> Length_delimited 311 | | Bytes -> Length_delimited 312 | | Msg mt -> Length_delimited 313 | 314 | | Fixed32 -> Thirty_two 315 | | Sfixed32 -> Thirty_two 316 | | Float -> Thirty_two 317 | 318 | | Varint -> Varint 319 | | Int32 -> Varint 320 | | Int64 -> Varint 321 | | Uint32 -> Varint 322 | | Uint64 -> Varint 323 | | Sint32 -> Varint 324 | | Sint64 -> Varint 325 | | Bool -> Varint 326 | | Enum _ -> Varint 327 | 328 | module Message (X: sig val name: string end) : MESSAGE = 329 | struct 330 | (** a message is a key↦value map *) 331 | type m = M 332 | 333 | let t = { msg_name = X.name; fields = [] } 334 | 335 | let field field_kind field_type name field_number = 336 | (* TODO: check for duplicate field numbers *) 337 | Field { key = { field_number = UInt64.of_int field_number; 338 | wire_type = wire_type field_type }; 339 | name; field_type; field_kind } 340 | 341 | (* TODO: handle 'packed' *) 342 | let repeated ?(packed=false) ft name n = 343 | let f = field (Repeated {packed}) ft name n in 344 | (* TODO: handle 'default' *) 345 | t.fields <- Boxed_field f :: t.fields; 346 | f 347 | 348 | let optional ?default ft name n = 349 | let f = field (Optional {default}) ft name n in 350 | t.fields <- Boxed_field f :: t.fields; 351 | f 352 | 353 | let required ft name n = 354 | let f = field Required ft name n in 355 | t.fields <- Boxed_field f :: t.fields; 356 | f 357 | end 358 | 359 | let create msg_type = { contents = KeyMap.empty } 360 | 361 | let dump : string -> Format.formatter -> unit = 362 | let module PP = struct 363 | let reads (t : _ field_type) s = 364 | match Angstrom.(parse_string ~consume:Prefix) (read_field t) s with 365 | | Result.Error s -> raise Parse_error 366 | | Result.Ok v -> v 367 | let fprintf = Format.fprintf 368 | let rec msg fmt m : unit = 369 | fprintf fmt "{@[@ "; 370 | KeyMap.iter m ~f:(fun ~key:k ~data -> 371 | fprintf fmt "@[@[%s@]@ =>@ @[%a@]@],@ " 372 | (UInt64.to_string k.field_number) 373 | fields (k.wire_type, data)); 374 | fprintf fmt "@]}" 375 | and list fmt f l = 376 | fprintf fmt "[@["; 377 | let len = List.length l in 378 | ListLabels.iteri l ~f:(fun i x -> 379 | fprintf fmt "@["; 380 | f fmt x; 381 | if succ i < len then fprintf fmt ",@;"; 382 | fprintf fmt "@]"; 383 | ); 384 | fprintf fmt "]@]" 385 | and fields fmt (wire_type, (fs : string list)) = 386 | let open Wire_type in 387 | match wire_type with 388 | | Varint -> list fmt varint fs 389 | | Sixty_four -> list fmt sixty_four fs 390 | | Length_delimited -> list fmt length_delimited fs 391 | | Start_group 392 | | End_group -> fprintf fmt "@[@]" 393 | | Thirty_two -> list fmt thirty_two fs 394 | and varint fmt s = 395 | let i64 = reads int64 s 396 | and _u64 = reads uint64 s in 397 | fprintf fmt "%Ld" i64 398 | and sixty_four fmt s = 399 | (** fixed64, sfixed64, double *) 400 | let _f64 = reads fixed64 s 401 | and sf64 = reads sfixed64 s 402 | and _d = reads double s in 403 | fprintf fmt "%Ld" sf64 404 | and thirty_two fmt s = 405 | (** fixed32, sfixed32, float *) 406 | let _f32 = reads fixed32 s 407 | and sf32 = reads sfixed32 s 408 | and _f = reads float s in 409 | fprintf fmt "%ld" sf32 410 | and length_delimited fmt s = 411 | (** string, bytes, embedded messages, packed repeated fields *) 412 | let b = reads string s in 413 | match Angstrom.(parse_string ~consume:Prefix) read_contents s with 414 | | Result.Ok m -> msg fmt m 415 | | Result.Error _ -> Format.fprintf fmt "@[%S@]" b 416 | end in 417 | fun s fmt -> 418 | match Angstrom.(parse_string ~consume:Prefix) read_contents s with 419 | | Result.Ok m -> Format.fprintf fmt "@[%a@]@." PP.msg m 420 | | Result.Error _ -> raise Parse_error 421 | 422 | let message name = 423 | (module Message (struct let name = name end) : MESSAGE) 424 | 425 | let read_from_string p s = 426 | match Angstrom.(parse_string ~consume:Prefix) p s with 427 | | Result.Error _ -> raise Parse_error 428 | | Result.Ok v -> v 429 | 430 | let getf : type m a. m msg -> (m, a) field -> a = 431 | fun msg (Field field) -> match field.field_kind with 432 | | Repeated _ -> 433 | begin match KeyMap.find field.key msg.contents with 434 | | exception Not_found -> [] 435 | | v -> List.rev_map (fun s -> read_from_string (read_field field.field_type) s) v 436 | end 437 | | Optional _ -> 438 | begin match KeyMap.find field.key msg.contents with 439 | | exception Not_found -> None 440 | | [] -> None 441 | | h :: _ -> Some (read_from_string (read_field field.field_type) h) 442 | end 443 | | Required -> 444 | begin match KeyMap.find field.key msg.contents with 445 | | [] -> raise Not_found 446 | | h :: _ -> read_from_string (read_field field.field_type) h 447 | end 448 | 449 | let setf : type m a. m msg -> (m, a) field -> a -> unit = 450 | fun msg (Field field) v -> 451 | match field.field_kind, v with 452 | | Repeated _, v -> 453 | let fields = List.map (string_of_field field.field_type) v in 454 | msg.contents <- KeyMap.add msg.contents ~key:field.key ~data:fields 455 | | Optional _, None -> 456 | msg.contents <- KeyMap.add msg.contents ~key:field.key ~data:[] 457 | | Optional _, Some v -> 458 | let fields = string_of_field field.field_type v in 459 | msg.contents <- KeyMap.add msg.contents ~key:field.key ~data:[fields] 460 | | Required, v -> 461 | let fields = string_of_field field.field_type v in 462 | msg.contents <- KeyMap.add msg.contents ~key:field.key ~data:[fields] 463 | 464 | (** Pretty-printing *) 465 | let pp_field_type : type a. Format.formatter -> a field_type -> unit = 466 | let pp_string = Format.pp_print_string in 467 | fun fmt -> function 468 | | Fixed64 -> pp_string fmt "fixed64" 469 | | Sfixed64 -> pp_string fmt "sfixed64" 470 | | Double -> pp_string fmt "double" 471 | | String -> pp_string fmt "string" 472 | | Bytes -> pp_string fmt "bytes" 473 | | Fixed32 -> pp_string fmt "fixed32" 474 | | Sfixed32 -> pp_string fmt "sfixed32" 475 | | Float -> pp_string fmt "float" 476 | | Varint -> pp_string fmt "varint" 477 | | Int32 -> pp_string fmt "int32" 478 | | Int64 -> pp_string fmt "int64" 479 | | Uint32 -> pp_string fmt "uint32" 480 | | Uint64 -> pp_string fmt "uint64" 481 | | Sint32 -> pp_string fmt "sint32" 482 | | Sint64 -> pp_string fmt "sint64" 483 | | Bool -> pp_string fmt "bool" 484 | | Msg {msg_name} -> 485 | (* TODO: print fields *) 486 | Format.fprintf fmt "Message:%s" msg_name 487 | | Enum _ -> pp_string fmt "" 488 | 489 | module PP = 490 | struct 491 | let pp_key fmt { field_number; wire_type } = 492 | Format.fprintf fmt "@[{@ field_number@ =@ %s;@ wire_type@ =@ %s}@]" 493 | (UInt64.to_string field_number) (Wire_type.to_string wire_type) 494 | 495 | let rec pp_field : type a. a field_type -> Format.formatter -> a -> unit = 496 | fun ft fmt v -> match ft with 497 | | Fixed64 -> Format.pp_print_string fmt (UInt64.to_string v) 498 | | Sfixed64 -> Format.fprintf fmt "%Ld" v 499 | | Double -> Format.fprintf fmt "%g" v 500 | | String -> Format.pp_print_string fmt v 501 | | Bytes -> Format.pp_print_string fmt (Bytes.to_string v) 502 | | Fixed32 -> Format.pp_print_string fmt (UInt32.to_string v) 503 | | Sfixed32 -> Format.fprintf fmt "%ld" v 504 | | Float -> Format.fprintf fmt "%g" v 505 | | Varint -> Format.pp_print_string fmt (UInt64.to_string v) 506 | | Int32 -> Format.fprintf fmt "%ld" v 507 | | Int64 -> Format.fprintf fmt "%Ld" v 508 | | Uint32 -> Format.pp_print_string fmt (UInt32.to_string v) 509 | | Uint64 -> Format.pp_print_string fmt (UInt64.to_string v) 510 | | Sint32 -> Format.fprintf fmt "%ld" v 511 | | Sint64 -> Format.fprintf fmt "%Ld" v 512 | | Bool -> Format.pp_print_bool fmt v 513 | | Msg msg_type -> pp_msg msg_type fmt v 514 | | Enum constants -> 515 | let Enum_constant c = v in 516 | match List.assoc c !constants with 517 | | s -> Format.pp_print_string fmt s 518 | | exception Not_found -> Format.fprintf fmt "%ld" c 519 | and pp_msg : type m.m msgtype -> Format.formatter -> m msg -> unit = 520 | fun msg_type fmt v -> 521 | Format.fprintf fmt "@[%s@;{@[%a@]}@]" msg_type.msg_name (pp_fields msg_type) v 522 | and pp_fields : type m.m msgtype -> Format.formatter -> m msg -> unit = 523 | fun msg_type fmt {contents=tbl} -> 524 | begin 525 | Format.fprintf fmt "@["; 526 | let last_field = pred (KeyMap.cardinal tbl) in 527 | let fields = List.sort 528 | (fun ({field_number=l},_) ({field_number=r},_) -> 529 | compare l r) @@ 530 | KeyMap.fold tbl ~init:[] 531 | ~f:(fun ~key:k ~data:v -> List.cons (k,v)) 532 | in 533 | ListLabels.iteri fields ~f:begin fun j (key, data) -> 534 | begin match ListLabels.find msg_type.fields 535 | ~f:(function Boxed_field (Field {key=k}) -> 536 | (* TODO: check wire types match *) 537 | key.field_number = k.field_number) with 538 | | exception Not_found -> 539 | (* No information available: dump the string. 540 | TODO: perhaps also print 'unknown field' *) 541 | Format.fprintf fmt "@[%a@ =>@ [@[" pp_key key; 542 | ListLabels.iter data ~f:(Format.fprintf fmt "@[%S,@]@ "); 543 | Format.fprintf fmt "]@]@]"; 544 | | Boxed_field (Field {field_type=ft; name}) -> 545 | (* We have field information available *) 546 | let last = pred (List.length data) in 547 | begin match data with 548 | [] -> () 549 | | [d] -> 550 | Format.fprintf fmt "@[%s@ =>@ %a" 551 | name 552 | (pp_field ft) (read_from_string (read_field ft) d); 553 | if j < last_field then Format.fprintf fmt ",@]@ " 554 | else Format.fprintf fmt "@]" 555 | | data -> 556 | Format.fprintf fmt "@[%s@ =>@ [@[" name; 557 | ListLabels.iteri data 558 | ~f:(fun i d -> 559 | if i < last then 560 | Format.fprintf fmt "@[%a,@]@ " (pp_field ft) (read_from_string (read_field ft) d) 561 | else 562 | Format.fprintf fmt "@[%a@]@," (pp_field ft) (read_from_string (read_field ft) d) 563 | ); 564 | if j < last_field then Format.fprintf fmt "],@]@]@;" 565 | else Format.fprintf fmt "]@]@]@;" 566 | end 567 | end; 568 | end; 569 | Format.fprintf fmt "@]"; 570 | end 571 | 572 | end 573 | 574 | let pp_field = PP.pp_field 575 | let pp_msg = PP.pp_msg 576 | 577 | (* Read a top-level message (not an embedded message) *) 578 | let read _ = Angstrom.(read_contents >>| fun contents -> { contents }) 579 | 580 | (* Write a top-level message (not an embedded message) *) 581 | let write m = 582 | let f = Faraday.create 1024 in write_contents f m.contents; f 583 | -------------------------------------------------------------------------------- /pb/src/pb.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | module Wire_type : sig 9 | type t = 10 | | Varint 11 | (** (0) Used for int32, int64, uint32, uint64, 12 | sint32, sint64, bool, enum *) 13 | | Sixty_four 14 | (** (1) Used for fixed64, sfixed64, double *) 15 | | Length_delimited 16 | (** (2) Used for string, bytes, embedded messages, 17 | packed repeated fields *) 18 | | Start_group 19 | (** (3) Used for groups (deprecated) *) 20 | | End_group 21 | (** (4) Used for groups (deprecated) *) 22 | | Thirty_two 23 | (** (5) Used for fixed32, sfixed32, float *) 24 | 25 | val of_int : int -> t 26 | 27 | val to_int : t -> int 28 | 29 | val to_string : t -> string 30 | 31 | val compare : t -> t -> int 32 | 33 | val pp : Format.formatter -> t -> unit 34 | end 35 | 36 | (** {1} Field types *) 37 | 38 | type 'a field_type 39 | (** The type of protobuf field encodings. *) 40 | 41 | val bool : bool field_type 42 | (** Varint-encoded booleans *) 43 | 44 | val int32 : int32 field_type 45 | (** Varint-encoded signed 32-bit integers. This is space-inefficient for 46 | negative numbers; it is recommended to use {!sint32} instead if negative 47 | numbers are likely to be frequent. *) 48 | 49 | val sint32 : int32 field_type 50 | (** Zigzag-encoded signed 32-bit integer. This is more space-efficient for 51 | negative numbers than {!int32}. *) 52 | 53 | val sfixed32 : int32 field_type 54 | (** Fixed-size encoding of signed 32-bit integers. *) 55 | 56 | val int64 : int64 field_type 57 | (** Varint-encoded signed 64-bit integers. This is space-inefficient for 58 | negative numbers; it is recommended to use {!sint64} instead if negative 59 | numbers are likely to be frequent. *) 60 | 61 | val sint64 : int64 field_type 62 | (** Zigzag-encoded signed 64-bit integer. This is more space-efficient for 63 | negative numbers than {!int64}. *) 64 | 65 | val sfixed64 : int64 field_type 66 | (** Fixed-size encoding of signed 64-bit integers. *) 67 | 68 | val uint32 : Unsigned.uint32 field_type 69 | (** Varint-encoded unsigned 32-bit integers. *) 70 | 71 | val fixed32 : Unsigned.uint32 field_type 72 | (** Fixed-size encoding of unsigned 32-bit integers. *) 73 | 74 | val uint64 : Unsigned.uint64 field_type 75 | (** Varint-encoded unsigned 64-bit integers. *) 76 | 77 | val fixed64 : Unsigned.uint64 field_type 78 | (** Fixed-size encoding of unsigned 64-bit integers. *) 79 | 80 | val double : float field_type 81 | (** Fixed-size encoding of 64-bit floating-point numbers. *) 82 | 83 | val float : float field_type 84 | (** Fixed-size encoding of 32-bit floating-point numbers. *) 85 | 86 | val string : string field_type 87 | (** Length-delimited encoding of UTF-8 strings. *) 88 | 89 | val bytes : Bytes.t field_type 90 | (** Length-delimited encoding of byte strings. *) 91 | 92 | type _ msg 93 | (** A value of type [msg] represents a message *) 94 | 95 | type _ msgtype 96 | (** A value of type [msgtype] represents a message type *) 97 | 98 | val msg : 'm msgtype -> 'm msg field_type 99 | (** Embedded message field type *) 100 | 101 | val read_field : 'a field_type -> 'a Angstrom.t 102 | (** [read_field t] returns an Angstrom parser for a field descsribed by 103 | [t]. *) 104 | 105 | val write_field : 'a field_type -> Faraday.t -> 'a -> unit 106 | (** [write_field t f v] serializes the bytes of the encoding of [v] 107 | described by [t], in order, to [f]. *) 108 | 109 | type _ enum 110 | 111 | module type ENUM = sig 112 | type e 113 | val t : e enum field_type 114 | val constant : string -> int32 -> e enum 115 | end 116 | 117 | val enum : string -> (module ENUM) 118 | (** Create a new enum type *) 119 | 120 | val constant_value : _ enum -> int32 121 | (** The value of a constant *) 122 | 123 | type (_,_) field 124 | (** A value of type [(m, t) field] describes a field of type [t] in a 125 | message of type [m]. *) 126 | 127 | module type MESSAGE = sig 128 | type m 129 | val t : m msgtype 130 | val optional : ?default:'a -> 'a field_type -> string -> int -> 131 | (m, 'a option) field 132 | val repeated : ?packed:bool -> 'a field_type -> string -> int -> 133 | (m, 'a list) field 134 | val required : 'a field_type -> string -> int -> 135 | (m, 'a) field 136 | end 137 | val message : string -> (module MESSAGE) 138 | (** Create a new message type *) 139 | 140 | val pp_field_type : Format.formatter -> 'a field_type -> unit 141 | (** Pretty-print a field type *) 142 | 143 | (** {3} Operations on values *) 144 | 145 | val getf : 'm msg -> ('m, 'a) field -> 'a 146 | (** Read a field of a message *) 147 | 148 | val setf : 'm msg -> ('m, 'a) field -> 'a -> unit 149 | (** Write a field of a message *) 150 | 151 | val pp_field : 'a field_type -> Format.formatter -> 'a -> unit 152 | (** Pretty-print a field value *) 153 | 154 | val pp_msg : 'a msgtype -> Format.formatter -> 'a msg -> unit 155 | (** Pretty-print a message *) 156 | 157 | val create : 'm msgtype -> 'm msg 158 | (** [create mt] creates a message of the type represented by [mt]. *) 159 | 160 | val read : 'm msgtype -> 'm msg Angstrom.t 161 | (** [read mt] returns an Angstrom parser for a message described by [mt]. *) 162 | 163 | val write : _ msg -> Faraday.t 164 | (** [write m] builds a Faraday serializer for [m]. *) 165 | 166 | exception Parse_error 167 | -------------------------------------------------------------------------------- /pb/src/wire_type.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | type t = 9 | | Varint 10 | (** (0) Used for int32, int64, uint32, uint64, 11 | sint32, sint64, bool, enum *) 12 | | Sixty_four 13 | (** (1) Used for fixed64, sfixed64, double *) 14 | | Length_delimited 15 | (** (2) Used for string, bytes, embedded messages, 16 | packed repeated fields *) 17 | | Start_group 18 | (** (3) Used for groups (deprecated) *) 19 | | End_group 20 | (** (4) Used for groups (deprecated) *) 21 | | Thirty_two 22 | (** (5) Used for fixed32, sfixed32, float *) 23 | 24 | let of_int = function 25 | | 0 -> Varint 26 | | 1 -> Sixty_four 27 | | 2 -> Length_delimited 28 | | 3 -> Start_group 29 | | 4 -> End_group 30 | | 5 -> Thirty_two 31 | | n -> Printf.kprintf invalid_arg "Wire_type.of_int (%d)" n 32 | 33 | let to_int = function 34 | | Varint -> 0 35 | | Sixty_four -> 1 36 | | Length_delimited -> 2 37 | | Start_group -> 3 38 | | End_group -> 4 39 | | Thirty_two -> 5 40 | 41 | let to_string = function 42 | | Varint -> "Varint" 43 | | Sixty_four -> "Sixty_four" 44 | | Length_delimited -> "Length_delimited" 45 | | Start_group -> "Start_group" 46 | | End_group -> "End_group" 47 | | Thirty_two -> "Thirty_two" 48 | 49 | let pp fmt v = Format.pp_print_string fmt (to_string v) 50 | 51 | let compare = compare 52 | -------------------------------------------------------------------------------- /pb/src/wire_type.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | type t = 9 | | Varint 10 | | Sixty_four 11 | | Length_delimited 12 | | Start_group 13 | | End_group 14 | | Thirty_two 15 | 16 | val of_int : int -> t 17 | 18 | val to_int : t -> int 19 | 20 | val to_string : t -> string 21 | 22 | val compare : t -> t -> int 23 | 24 | val pp : Format.formatter -> t -> unit 25 | -------------------------------------------------------------------------------- /pb/test/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names test test_messages) 3 | (libraries ounit2 faraday integers angstrom pb)) 4 | 5 | (alias 6 | (name pbs_python) 7 | (deps comprehensive.python.serialized small.python.serialized 8 | twostring.python.serialized)) 9 | 10 | (alias 11 | (name pbs_ocaml) 12 | (deps comprehensive.ocaml.serialized small.ocaml.serialized 13 | twostring.ocaml.serialized)) 14 | 15 | (rule 16 | (deps 17 | (:< test.proto)) 18 | (targets test_pb2.py) 19 | (action 20 | (run protoc %{<} --python_out=.))) 21 | 22 | (rule 23 | (deps 24 | (:< test_gen.py) 25 | test_pb2.py) 26 | (targets comprehensive.python.serialized small.python.serialized 27 | twostring.python.serialized) 28 | (action 29 | (setenv 30 | PYTHONPATH 31 | . 32 | (run python3 %{<})))) 33 | 34 | (rule 35 | (deps (alias pbs_python)) 36 | (targets comprehensive.ocaml.serialized small.ocaml.serialized 37 | twostring.ocaml.serialized) 38 | (action 39 | (run ./test.exe))) 40 | 41 | (alias 42 | (name runtest) 43 | (deps (alias pbs_python)) 44 | (action (run ./test.exe))) 45 | 46 | (alias 47 | (name runtest) 48 | (deps (alias pbs_python)) 49 | (action (run ./test_messages.exe))) 50 | 51 | (alias 52 | (name runtest) 53 | (deps 54 | (:< test_read.py) 55 | (alias pbs_ocaml) 56 | test_pb2.py) 57 | (action 58 | (setenv 59 | PYTHONPATH 60 | . 61 | (run python3 %{<})))) 62 | -------------------------------------------------------------------------------- /pb/test/test.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2017 Jeremy Yallop . 3 | * 4 | * This file is distributed under the terms of the MIT License. 5 | * See the file LICENSE for details. 6 | *) 7 | 8 | open OUnit2 9 | open Unsigned 10 | open Pb 11 | open Test_messages 12 | 13 | let to_string t v = 14 | let f = Faraday.create 128 in 15 | write_field t f v; 16 | Faraday.serialize_to_string f 17 | 18 | let msg_to_string m = Faraday.serialize_to_string (write m) 19 | 20 | let read_from_string p s = 21 | match Angstrom.(parse_string ~consume:Prefix) p s with 22 | | Result.Error _ -> Printf.kprintf failwith "parse failure (%s)" s 23 | | Result.Ok v -> v 24 | 25 | let of_string t s = read_from_string (read_field t) s 26 | 27 | let slurp filename = 28 | let {Unix.st_size=bytes;_} = Unix.stat filename in 29 | let fd = open_in filename in 30 | let s = really_input_string fd bytes in 31 | close_in fd; 32 | s 33 | 34 | let spew s filename = 35 | let fd = open_out filename in 36 | output_string fd s; 37 | close_out fd 38 | 39 | let test_roundtrip _ = 40 | let check_roundtrip ?printer ?msg t v = 41 | (* A comparison function that respects NaN's is necessary. (=) does not work 42 | because: 43 | compare nan nan = 0 44 | (nan = nan) = false *) 45 | assert_equal ?printer ?msg v (of_string t (to_string t v)) 46 | ~cmp:(fun x y -> compare x y = 0) 47 | in 48 | begin 49 | check_roundtrip bool true; 50 | check_roundtrip bool false; 51 | 52 | (* Signed 32-bit integer encodings *) 53 | check_roundtrip int32 Int32.zero 54 | ~msg:"roundtrip int32 zero"; 55 | check_roundtrip int32 Int32.min_int 56 | ~msg:"roundtrip int32 min_int"; 57 | check_roundtrip int32 Int32.max_int 58 | ~msg:"roundtrip int32 max_int"; 59 | 60 | check_roundtrip sint32 Int32.zero 61 | ~msg:"roundtrip sint32 zero"; 62 | check_roundtrip sint32 Int32.min_int 63 | ~msg:"roundtrip sint32 min_int" ~printer:Int32.to_string; 64 | check_roundtrip sint32 Int32.max_int 65 | ~msg:"roundtrip sint32 max_int"; 66 | 67 | check_roundtrip sfixed32 Int32.zero 68 | ~msg:"roundtrip sfixed32 zero"; 69 | check_roundtrip sfixed32 Int32.min_int 70 | ~msg:"roundtrip sfixed32 min_int"; 71 | check_roundtrip sfixed32 Int32.max_int 72 | ~msg:"roundtrip sfixed32 max_int"; 73 | 74 | (* Signed 64-bit integer encodings *) 75 | check_roundtrip int64 Int64.zero 76 | ~msg:"roundtrip int64 zero"; 77 | check_roundtrip int64 Int64.min_int 78 | ~msg:"roundtrip int64 min_int"; 79 | check_roundtrip int64 Int64.max_int 80 | ~msg:"roundtrip int64 max_int"; 81 | 82 | check_roundtrip sint64 Int64.zero 83 | ~msg:"roundtrip sint64 zero"; 84 | check_roundtrip sint64 Int64.min_int 85 | ~msg:"roundtrip sint64 min_int"; 86 | check_roundtrip sint64 Int64.max_int 87 | ~msg:"roundtrip sint64 max_int"; 88 | 89 | check_roundtrip sfixed64 Int64.zero 90 | ~msg:"roundtrip sfixed64 zero"; 91 | check_roundtrip sfixed64 Int64.one 92 | ~msg:"roundtrip sfixed64 one"; 93 | check_roundtrip sfixed64 128L 94 | ~msg:"roundtrip sfixed64 128"; 95 | check_roundtrip sfixed64 (-1L) 96 | ~msg:"roundtrip sfixed64 -1"; 97 | check_roundtrip sfixed64 (-128L) 98 | ~msg:"roundtrip sfixed64 -128"; 99 | check_roundtrip sfixed64 Int64.min_int 100 | ~msg:"roundtrip sfixed64 min_int"; 101 | check_roundtrip sfixed64 Int64.max_int 102 | ~msg:"roundtrip sfixed64 max_int"; 103 | 104 | 105 | (* Unsigned 32-bit integer encodings *) 106 | check_roundtrip uint32 UInt32.zero 107 | ~msg:"roundtrip uint32 zero"; 108 | check_roundtrip uint32 UInt32.one 109 | ~msg:"roundtrip uint32 one"; 110 | check_roundtrip uint32 UInt32.max_int 111 | ~msg:"roundtrip uint32 max_int"; 112 | 113 | check_roundtrip fixed32 UInt32.zero 114 | ~msg:"roundtrip fixed32 zero"; 115 | check_roundtrip fixed32 UInt32.one 116 | ~msg:"roundtrip fixed32 one"; 117 | check_roundtrip fixed32 UInt32.max_int 118 | ~msg:"roundtrip fixed32 max_int"; 119 | 120 | 121 | (* Unsigned 64-bit integer encodings *) 122 | check_roundtrip uint64 UInt64.zero 123 | ~msg:"roundtrip uint64 zero"; 124 | check_roundtrip uint64 UInt64.one 125 | ~msg:"roundtrip uint64 one"; 126 | check_roundtrip uint64 UInt64.max_int 127 | ~msg:"roundtrip uint64 max_int"; 128 | 129 | check_roundtrip fixed64 UInt64.zero 130 | ~msg:"roundtrip fixed64 zero"; 131 | check_roundtrip fixed64 UInt64.one 132 | ~msg:"roundtrip fixed64 one"; 133 | check_roundtrip fixed64 UInt64.max_int 134 | ~msg:"roundtrip fixed64 max_int"; 135 | 136 | (* Floating point encodings *) 137 | check_roundtrip ~printer:string_of_float double 0.0 138 | ~msg:"roundtrip double 0.0"; 139 | check_roundtrip ~printer:string_of_float double 1.0 140 | ~msg:"roundtrip double 1.0"; 141 | check_roundtrip ~printer:string_of_float double nan 142 | ~msg:"roundtrip double nan"; 143 | check_roundtrip ~printer:string_of_float double infinity 144 | ~msg:"roundtrip double infinity"; 145 | 146 | check_roundtrip ~printer:string_of_float float 0.0 147 | ~msg:"roundtrip float 0.0"; 148 | check_roundtrip ~printer:string_of_float float 1.0 149 | ~msg:"roundtrip float 1.0"; 150 | check_roundtrip ~printer:string_of_float float nan 151 | ~msg:"roundtrip float nan"; 152 | check_roundtrip ~printer:string_of_float float infinity 153 | ~msg:"roundtrip float infinity"; 154 | 155 | 156 | (* Strings *) 157 | check_roundtrip string "" 158 | ~msg:"roundtrip string \"\"" 159 | ~printer:(Printf.sprintf "%S"); 160 | check_roundtrip string "abc" 161 | ~msg:"roundtrip string \"abc\""; 162 | check_roundtrip string "\000\001\002\003\004\005\006\007\b\t\n\011\012\r\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\127\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255" 163 | ~msg:"roundtrip string \"(full range)\""; 164 | 165 | check_roundtrip bytes (Bytes.of_string "") 166 | ~msg:"roundtrip bytes \"\""; 167 | check_roundtrip bytes (Bytes.of_string "abc") 168 | ~msg:"roundtrip bytes \"abc\""; 169 | check_roundtrip bytes (Bytes.of_string "\000\001\002\003\004\005\006\007\b\t\n\011\012\r\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\127\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255") 170 | ~msg:"roundtrip bytes \"(full range)\""; 171 | 172 | (* Messages *) 173 | let module M = struct 174 | module M1 = (val message "M1") 175 | let f1 = M1.repeated int32 "f1" 1 176 | let f2 = M1.repeated string "f2" 2 177 | 178 | module M2 = (val message "M2") 179 | let f4 = M2.repeated (msg M1.t) "f4" 15 180 | let f5 = M2.repeated sfixed64 "f5" 20 181 | end in 182 | let open M in 183 | 184 | let m1 = create M1.t in 185 | setf m1 f1 [77l]; 186 | setf m1 f2 ["abc"; "def"]; 187 | 188 | let s1 = to_string (msg M1.t) m1 in 189 | let m1' = of_string (msg M1.t) s1 in 190 | assert_equal [77l] (getf m1' f1); 191 | assert_equal ["abc"; "def"] (getf m1' f2); 192 | 193 | let m2 = create M2.t in 194 | setf m2 f5 [1001L]; 195 | setf m2 f4 [m1]; 196 | 197 | let s2 = to_string (msg M2.t) m2 in 198 | let m2' = of_string (msg M2.t) s2 in 199 | 200 | assert_equal [1001L] (getf m2' f5); 201 | let m1'' = List.hd (getf m2' f4) in 202 | assert_equal [77l] (getf m1'' f1); 203 | assert_equal ["abc"; "def"] (getf m1'' f2); 204 | end 205 | 206 | let test_messages _ = 207 | let s = "\n\x06Robert" in 208 | let module Hello = (val message "Hello") in 209 | let name = Hello.required string "name" 1 in 210 | let m = read_from_string (read Hello.t) s in 211 | assert_equal "Robert" (getf m name) 212 | 213 | 214 | let test_interoperability_small_read _ = 215 | let small_string = slurp "small.python.serialized" in 216 | let small = read_from_string (read Small.S.t) small_string in 217 | begin 218 | assert (getf small Small.s = Some "abc"); 219 | assert (getf small Small.i = Some 17L); 220 | end 221 | 222 | 223 | let test_interoperability_twostring_read _ = 224 | let two_string = slurp "twostring.python.serialized" in 225 | let two = read_from_string (read TwoString.T.t) two_string in 226 | begin 227 | assert (getf two TwoString.s = "abc"); 228 | assert (getf two TwoString.b = "def"); 229 | end 230 | 231 | let test_interoperability_comprehensive_read _ = 232 | let comprehensive_string = slurp "comprehensive.python.serialized" in 233 | let c = read_from_string (read Comprehensive.C.t) comprehensive_string in 234 | begin 235 | let u32 = UInt32.of_int and u64 = UInt64.of_int and bytes = Bytes.of_string in 236 | assert (getf c Comprehensive.repeated_uint32 = [u32 1; u32 2]); 237 | assert (getf c Comprehensive.required_int32 = 3_l); 238 | let s = getf c Comprehensive.required_Small in 239 | assert (getf s Small.s = Some "abc"); 240 | assert (getf s Small.i = Some 17L); 241 | assert (getf c Comprehensive.required_double = 4.1); 242 | assert (getf c Comprehensive.optional_sfixed32 = Some 5_l); 243 | assert (getf c Comprehensive.optional_fixed32 = Some (u32 6)); 244 | assert (getf c Comprehensive.repeated_bytes = [bytes "def"; bytes "gh"]); 245 | assert (getf c Comprehensive.repeated_bool = [false; true]); 246 | assert (getf c Comprehensive.repeated_sfixed64 = [7_L; 8_L; 9_L]); 247 | assert (getf c Comprehensive.optional_bool = Some true); 248 | assert (getf c Comprehensive.required_uint32 = u32 10); 249 | assert (getf c Comprehensive.optional_double = Some 11.2); 250 | assert (getf c Comprehensive.required_int64 = 12_L); 251 | assert (getf c Comprehensive.required_uint64 = u64 13); 252 | assert (getf c Comprehensive.required_string = "rstuvw"); 253 | assert (getf c Comprehensive.required_bytes = bytes "lmnopq"); 254 | assert (getf c Comprehensive.optional_bytes = Some (bytes "rstuv")); 255 | assert (getf c Comprehensive.optional_sint64 = Some 14_L); 256 | assert (getf c Comprehensive.repeated_sint64 = [-15_L; 16_L; 17_L]); 257 | assert (getf c Comprehensive.repeated_fixed32 = [u32 18; u32 19; u32 20; u32 21]); 258 | let () = match getf c Comprehensive.optional_Small with 259 | None -> assert false 260 | | Some s -> 261 | assert (getf s Small.s = Some "abc"); 262 | assert (getf s Small.i = Some 17L); 263 | in 264 | assert (getf c Comprehensive.optional_int32 = Some 22_l); 265 | assert (getf c Comprehensive.optional_fixed64 = Some (u64 23)); 266 | assert (getf c Comprehensive.optional_enum = Some Enum.one); 267 | assert (getf c Comprehensive.required_float = 24.5); 268 | assert (getf c Comprehensive.optional_sfixed64 = Some 25_L); 269 | assert (getf c Comprehensive.required_sfixed32 = 26_l); 270 | assert (getf c Comprehensive.required_bool = true); 271 | assert (getf c Comprehensive.repeated_fixed64 = [u64 27; u64 28; u64 29; u64 30; u64 31]); 272 | assert (getf c Comprehensive.optional_sint32 = Some 32_l); 273 | assert (getf c Comprehensive.repeated_int64 = [33_L; 34_L; 35_L; 36_L; 37_L; 38_L; 39_L]); 274 | assert (getf c Comprehensive.required_fixed64 = u64 40); 275 | assert (getf c Comprehensive.repeated_enum = [Enum.one; Enum.two]); 276 | assert (getf c Comprehensive.optional_int64 = Some 41_L); 277 | assert (getf c Comprehensive.repeated_float = [42.0]); 278 | assert (getf c Comprehensive.repeated_sint32 = [44_l; 45_l; 46_l; 47_l; 48_l; 49_l]); 279 | assert (getf c Comprehensive.repeated_uint64 = [u64 50; u64 51; u64 52; u64 53; u64 54; u64 55]); 280 | let () = match getf c Comprehensive.repeated_Small with 281 | [s1; s2] -> 282 | assert (getf s2 Small.s = None); 283 | assert (getf s2 Small.i = Some 100L); 284 | assert (getf s1 Small.s = Some "abc"); 285 | assert (getf s1 Small.i = Some 17L); 286 | | _ -> assert false 287 | in 288 | assert (getf c Comprehensive.repeated_double = [56.3; 57.4; 58.0; 59.1]); 289 | assert (getf c Comprehensive.repeated_string = ["w"; ""; "yz"]); 290 | assert (getf c Comprehensive.required_sfixed64 = 60_L); 291 | assert (getf c Comprehensive.required_sint64 = 61_L); 292 | assert (getf c Comprehensive.optional_string = Some "A3"); 293 | assert (getf c Comprehensive.optional_uint32 = Some (u32 62)); 294 | assert (getf c Comprehensive.repeated_sfixed32 = [63_l; 64_l; 65_l; 66_l; 67_l; 68_l]); 295 | assert (getf c Comprehensive.optional_float = Some 69.0); 296 | assert (getf c Comprehensive.optional_uint64 = Some (u64 70)); 297 | assert (getf c Comprehensive.required_enum = Enum.two); 298 | assert (getf c Comprehensive.required_sint32 = 71_l); 299 | assert (getf c Comprehensive.required_fixed32 = u32 72); 300 | assert (getf c Comprehensive.repeated_int32 = [73_l; 74_l; 75_l; 76_l; 77_l; 78_l]); 301 | end 302 | 303 | 304 | let test_interoperability_small_write _ = 305 | let small = create Small.S.t in 306 | begin 307 | setf small Small.s (Some "abc"); 308 | setf small Small.i (Some 17_L); 309 | spew (msg_to_string small) "small.ocaml.serialized"; 310 | end 311 | 312 | 313 | let test_interoperability_twostring_write _ = 314 | let two = create TwoString.T.t in 315 | begin 316 | setf two TwoString.s "abc"; 317 | setf two TwoString.b "def"; 318 | spew (msg_to_string two) "twostring.ocaml.serialized"; 319 | end 320 | 321 | 322 | let test_interoperability_comprehensive_write _ = 323 | let c = create Comprehensive.C.t in 324 | begin 325 | let u32 = UInt32.of_int and u64 = UInt64.of_int and bytes = Bytes.of_string in 326 | setf c Comprehensive.repeated_uint32 [u32 1; u32 2]; 327 | setf c Comprehensive.required_int32 3_l; 328 | let s = create Small.S.t in 329 | setf s Small.s (Some "abc"); 330 | setf s Small.i (Some 17L); 331 | setf c Comprehensive.required_Small s; 332 | setf c Comprehensive.required_double 4.1; 333 | setf c Comprehensive.optional_sfixed32 (Some 5_l); 334 | setf c Comprehensive.optional_fixed32 (Some (u32 6)); 335 | setf c Comprehensive.repeated_bytes [bytes "def"; bytes "gh"]; 336 | setf c Comprehensive.repeated_bool [false; true]; 337 | setf c Comprehensive.repeated_sfixed64 [7_L; 8_L; 9_L]; 338 | setf c Comprehensive.optional_bool (Some true); 339 | setf c Comprehensive.required_uint32 (u32 10); 340 | setf c Comprehensive.optional_double (Some 11.2); 341 | setf c Comprehensive.required_int64 12_L; 342 | setf c Comprehensive.required_uint64 (u64 13); 343 | setf c Comprehensive.required_string "rstuvw"; 344 | setf c Comprehensive.required_bytes (bytes "lmnopq"); 345 | setf c Comprehensive.optional_bytes (Some (bytes "rstuv")); 346 | setf c Comprehensive.optional_sint64 (Some 14_L); 347 | setf c Comprehensive.repeated_sint64 [-15_L; 16_L; 17_L]; 348 | setf c Comprehensive.repeated_fixed32 [u32 18; u32 19; u32 20; u32 21]; 349 | let s = create Small.S.t in 350 | setf s Small.s (Some "abc"); 351 | setf s Small.i (Some 17L); 352 | setf c Comprehensive.optional_Small (Some s); 353 | setf c Comprehensive.optional_int32 (Some 22_l); 354 | setf c Comprehensive.optional_fixed64 (Some (u64 23)); 355 | setf c Comprehensive.optional_enum (Some Enum.one); 356 | setf c Comprehensive.required_float 24.5; 357 | setf c Comprehensive.optional_sfixed64 (Some 25_L); 358 | setf c Comprehensive.required_sfixed32 26_l; 359 | setf c Comprehensive.required_bool true; 360 | setf c Comprehensive.repeated_fixed64 [u64 27; u64 28; u64 29; u64 30; u64 31]; 361 | setf c Comprehensive.optional_sint32 (Some 32_l); 362 | setf c Comprehensive.repeated_int64 [33_L; 34_L; 35_L; 36_L; 37_L; 38_L; 100000000039_L]; 363 | setf c Comprehensive.required_fixed64 (u64 40); 364 | setf c Comprehensive.repeated_enum [Enum.one; Enum.two]; 365 | setf c Comprehensive.optional_int64 (Some 41_L); 366 | setf c Comprehensive.repeated_float [42.0]; 367 | setf c Comprehensive.repeated_sint32 [44_l; 45_l; 46_l; 47_l; 48_l; 49_l]; 368 | setf c Comprehensive.repeated_uint64 [u64 50; u64 51; u64 52; u64 53; u64 54; u64 55]; 369 | let s1 = create Small.S.t and s2 = create Small.S.t in 370 | setf s2 Small.s None; 371 | setf s2 Small.i (Some 100L); 372 | setf s1 Small.s (Some "abc"); 373 | setf s1 Small.i (Some 17L); 374 | setf c Comprehensive.repeated_Small [s1; s2]; 375 | setf c Comprehensive.repeated_double [56.3; 57.4; 58.0; 59.1]; 376 | setf c Comprehensive.repeated_string ["w"; ""; "yz"]; 377 | setf c Comprehensive.required_sfixed64 60_L; 378 | setf c Comprehensive.required_sint64 61_L; 379 | setf c Comprehensive.optional_string (Some "A3"); 380 | setf c Comprehensive.optional_uint32 (Some (u32 62)); 381 | setf c Comprehensive.repeated_sfixed32 [63_l; 64_l; 65_l; 66_l; 67_l; 68_l]; 382 | setf c Comprehensive.optional_float (Some 69.0); 383 | setf c Comprehensive.optional_uint64 (Some (u64 70)); 384 | setf c Comprehensive.required_enum Enum.two; 385 | setf c Comprehensive.required_sint32 71_l; 386 | setf c Comprehensive.required_fixed32 (u32 72); 387 | setf c Comprehensive.repeated_int32 [73_l; 74_l; 75_l; 76_l; 77_l; 78_l]; 388 | spew (msg_to_string c) "comprehensive.ocaml.serialized"; 389 | end 390 | 391 | 392 | let suite = "Protobuf wire format tests" >::: 393 | ["test roundtrip" 394 | >:: test_roundtrip; 395 | 396 | "test messages" 397 | >:: test_messages; 398 | 399 | "test interoperability (small read)" 400 | >:: test_interoperability_small_read; 401 | 402 | "test interoperability (twostring read)" 403 | >:: test_interoperability_twostring_read; 404 | 405 | "test interoperability (comprehensive read)" 406 | >:: test_interoperability_comprehensive_read; 407 | 408 | "test interoperability (small write)" 409 | >:: test_interoperability_small_write; 410 | 411 | "test interoperability (twostring write)" 412 | >:: test_interoperability_twostring_write; 413 | 414 | "test interoperability (comprehensive write)" 415 | >:: test_interoperability_comprehensive_write; 416 | ] 417 | 418 | 419 | let _ = 420 | run_test_tt_main suite 421 | -------------------------------------------------------------------------------- /pb/test/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | enum Enum { 4 | one = 1; 5 | two = 2; 6 | } 7 | 8 | message Small { 9 | optional string small_s = 100; 10 | optional int64 small_i = 200; 11 | } 12 | 13 | 14 | message TwoString { 15 | required string two_s = 1000; 16 | required string two_b = 2000; 17 | } 18 | 19 | message Comprehensive { 20 | repeated uint32 repeated_uint32 = 1; 21 | required int32 required_int32 = 2; 22 | required Small required_Small = 3; 23 | required double required_double = 4; 24 | optional sfixed32 optional_sfixed32 = 5; 25 | optional fixed32 optional_fixed32 = 6; 26 | repeated bytes repeated_bytes = 7; 27 | repeated bool repeated_bool = 8; 28 | repeated sfixed64 repeated_sfixed64 = 9; 29 | optional bool optional_bool = 10; 30 | required uint32 required_uint32 = 11; 31 | optional double optional_double = 12; 32 | required int64 required_int64 = 13; 33 | required uint64 required_uint64 = 14; 34 | required string required_string = 15; 35 | required string required_bytes = 16; 36 | optional bytes optional_bytes = 17; 37 | optional sint64 optional_sint64 = 18; 38 | repeated sint64 repeated_sint64 = 19; 39 | repeated fixed32 repeated_fixed32 = 20; 40 | optional Small optional_Small = 21; 41 | optional int32 optional_int32 = 22; 42 | optional fixed64 optional_fixed64 = 23; 43 | optional Enum optional_enum = 24; 44 | required float required_float = 25; 45 | optional sfixed64 optional_sfixed64 = 26; 46 | required sfixed32 required_sfixed32 = 27; 47 | required bool required_bool = 28; 48 | repeated fixed64 repeated_fixed64 = 29; 49 | optional sint32 optional_sint32 = 30; 50 | repeated int64 repeated_int64 = 31; 51 | required fixed64 required_fixed64 = 32; 52 | repeated Enum repeated_enum = 33; 53 | optional int64 optional_int64 = 34; 54 | repeated float repeated_float = 35; 55 | repeated sint32 repeated_sint32 = 36; 56 | repeated uint64 repeated_uint64 = 37; 57 | repeated Small repeated_Small = 38; 58 | repeated double repeated_double = 39; 59 | repeated string repeated_string = 40; 60 | required sfixed64 required_sfixed64 = 41; 61 | required sint64 required_sint64 = 42; 62 | optional string optional_string = 43; 63 | optional uint32 optional_uint32 = 44; 64 | repeated sfixed32 repeated_sfixed32 = 45; 65 | optional float optional_float = 46; 66 | optional uint64 optional_uint64 = 47; 67 | required Enum required_enum = 48; 68 | required sint32 required_sint32 = 49; 69 | required fixed32 required_fixed32 = 50; 70 | repeated int32 repeated_int32 = 51; 71 | } 72 | -------------------------------------------------------------------------------- /pb/test/test_gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import test_pb2 4 | 5 | e1 = test_pb2.Enum.Value('one') 6 | e2 = test_pb2.Enum.Value('two') 7 | 8 | s1 = test_pb2.Small() 9 | s1.small_s = 'abc' 10 | s1.small_i = 17 11 | 12 | s2 = test_pb2.Small() 13 | s2.small_i = 100 14 | 15 | two = test_pb2.TwoString() 16 | two.two_s = 'abc' 17 | two.two_b = 'def' 18 | 19 | c = test_pb2.Comprehensive() 20 | c.repeated_uint32.extend([1,2]) 21 | c.required_int32 = 3 22 | c.required_Small.CopyFrom(s1) 23 | c.required_double = 4.1 24 | c.optional_sfixed32 = 5 25 | c.optional_fixed32 = 6 26 | c.repeated_bytes.extend([b'def', b'gh']) 27 | c.repeated_bool.extend([False, True]) 28 | c.repeated_sfixed64.extend([7,8,9]) 29 | c.optional_bool = True 30 | c.required_uint32 = 10 31 | c.optional_double = 11.2 32 | c.required_int64 = 12 33 | c.required_uint64 = 13 34 | c.required_string = 'rstuvw' 35 | c.required_bytes = b'lmnopq' 36 | c.optional_bytes = b'rstuv' 37 | c.optional_sint64 = 14 38 | c.repeated_sint64.extend([-15,16,17]) 39 | c.repeated_fixed32.extend([18,19,20,21]) 40 | c.optional_Small.CopyFrom(s1) 41 | c.optional_int32 = 22 42 | c.optional_fixed64 = 23 43 | c.optional_enum = e1 44 | c.required_float = 24.5 45 | c.optional_sfixed64 = 25 46 | c.required_sfixed32 = 26 47 | c.required_bool = True 48 | c.repeated_fixed64.extend([27,28,29,30,31]) 49 | c.optional_sint32 = 32 50 | c.repeated_int64.extend([33,34,35,36,37,38,39]) 51 | c.required_fixed64 = 40 52 | c.repeated_enum.extend([e1, e2]) 53 | c.optional_int64 = 41 54 | c.repeated_float.extend([42.0]) 55 | c.repeated_sint32.extend([44, 45, 46, 47, 48, 49]) 56 | c.repeated_uint64.extend([50, 51, 52, 53, 54, 55]) 57 | c.repeated_Small.extend([s1, s2]) 58 | c.repeated_double.extend([56.3, 57.4, 58.0, 59.1]) 59 | c.repeated_string.extend(['w', '', 'yz']) 60 | c.required_sfixed64 = 60 61 | c.required_sint64 = 61 62 | c.optional_string = 'A3' 63 | c.optional_uint32 = 62 64 | c.repeated_sfixed32.extend([63, 64, 65, 66, 67, 68]) 65 | c.optional_float = 69.0 66 | c.optional_uint64 = 70 67 | c.required_enum = e2 68 | c.required_sint32 = 71 69 | c.required_fixed32 = 72 70 | c.repeated_int32.extend([73, 74, 75, 76, 77, 78]) 71 | 72 | def main(): 73 | print('generating comprehensive.serialized') 74 | with open('comprehensive.python.serialized', 'wb') as fd: 75 | fd.write(c.SerializeToString()) 76 | 77 | print('generating small.serialized') 78 | with open('small.python.serialized', 'wb') as fd: 79 | fd.write(s1.SerializeToString()) 80 | 81 | print('generating twostring.serialized') 82 | with open('twostring.python.serialized', 'wb') as fd: 83 | fd.write(two.SerializeToString()) 84 | 85 | if __name__ == '__main__': 86 | main() 87 | -------------------------------------------------------------------------------- /pb/test/test_messages.ml: -------------------------------------------------------------------------------- 1 | open Pb 2 | 3 | module Enum = 4 | struct 5 | module E = (val enum "Enum") 6 | let one = E.constant "one" 1_l 7 | let two = E.constant "two" 2_l 8 | end 9 | 10 | module Small = 11 | struct 12 | module S = (val message "Small") 13 | let s = S.optional string "small_s" 100 14 | let i = S.optional int64 "small_i" 200 15 | end 16 | 17 | module TwoString = 18 | struct 19 | module T = (val message "TwoString") 20 | let s = T.required string "two_s" 1000 21 | let b = T.required string "two_b" 2000 22 | end 23 | 24 | module Comprehensive = 25 | struct 26 | module C = (val message "Comprehensive") 27 | let repeated_uint32 = 28 | C.repeated uint32 "repeated_uint32" 1 29 | let required_int32 = 30 | C.required int32 "required_int32" 2 31 | let required_Small = 32 | C.required (msg Small.S.t) "required_Small" 3 33 | let required_double = 34 | C.required double "required_double" 4 35 | let optional_sfixed32 = 36 | C.optional sfixed32 "optional_sfixed32" 5 37 | let optional_fixed32 = 38 | C.optional fixed32 "optional_fixed32" 6 39 | let repeated_bytes = 40 | C.repeated bytes "repeated_bytes" 7 41 | let repeated_bool = 42 | C.repeated bool "repeated_bool" 8 43 | let repeated_sfixed64 = 44 | C.repeated sfixed64 "repeated_sfixed64" 9 45 | let optional_bool = 46 | C.optional bool "optional_bool" 10 47 | let required_uint32 = 48 | C.required uint32 "required_uint32" 11 49 | let optional_double = 50 | C.optional double "optional_double" 12 51 | let required_int64 = 52 | C.required int64 "required_int64" 13 53 | let required_uint64 = 54 | C.required uint64 "required_uint64" 14 55 | let required_string = 56 | C.required string "required_string" 15 57 | let required_bytes = 58 | C.required bytes "required_bytes" 16 59 | let optional_bytes = 60 | C.optional bytes "optional_bytes" 17 61 | let optional_sint64 = 62 | C.optional sint64 "optional_sint64" 18 63 | let repeated_sint64 = 64 | C.repeated sint64 "repeated_sint64" 19 65 | let repeated_fixed32 = 66 | C.repeated fixed32 "repeated_fixed32" 20 67 | let optional_Small = 68 | C.optional (msg Small.S.t) "optional_Small" 21 69 | let optional_int32 = 70 | C.optional int32 "optional_int32" 22 71 | let optional_fixed64 = 72 | C.optional fixed64 "optional_fixed64" 23 73 | let optional_enum = 74 | C.optional Enum.E.t "optional_enum" 24 75 | let required_float = 76 | C.required float "required_float" 25 77 | let optional_sfixed64 = 78 | C.optional sfixed64 "optional_sfixed64" 26 79 | let required_sfixed32 = 80 | C.required sfixed32 "required_sfixed32" 27 81 | let required_bool = 82 | C.required bool "required_bool" 28 83 | let repeated_fixed64 = 84 | C.repeated fixed64 "repeated_fixed64" 29 85 | let optional_sint32 = 86 | C.optional sint32 "optional_sint32" 30 87 | let repeated_int64 = 88 | C.repeated int64 "repeated_int64" 31 89 | let required_fixed64 = 90 | C.required fixed64 "required_fixed64" 32 91 | let repeated_enum = 92 | C.repeated Enum.E.t "repeated_enum" 33 93 | let optional_int64 = 94 | C.optional int64 "optional_int64" 34 95 | let repeated_float = 96 | C.repeated float "repeated_float" 35 97 | let repeated_sint32 = 98 | C.repeated sint32 "repeated_sint32" 36 99 | let repeated_uint64 = 100 | C.repeated uint64 "repeated_uint64" 37 101 | let repeated_Small = 102 | C.repeated (msg Small.S.t) "repeated_Small" 38 103 | let repeated_double = 104 | C.repeated double "repeated_double" 39 105 | let repeated_string = 106 | C.repeated string "repeated_string" 40 107 | let required_sfixed64 = 108 | C.required sfixed64 "required_sfixed64" 41 109 | let required_sint64 = 110 | C.required sint64 "required_sint64" 42 111 | let optional_string = 112 | C.optional string "optional_string" 43 113 | let optional_uint32 = 114 | C.optional uint32 "optional_uint32" 44 115 | let repeated_sfixed32 = 116 | C.repeated sfixed32 "repeated_sfixed32" 45 117 | let optional_float = 118 | C.optional float "optional_float" 46 119 | let optional_uint64 = 120 | C.optional uint64 "optional_uint64" 47 121 | let required_enum = 122 | C.required Enum.E.t "required_enum" 48 123 | let required_sint32 = 124 | C.required sint32 "required_sint32" 49 125 | let required_fixed32 = 126 | C.required fixed32 "required_fixed32" 50 127 | let repeated_int32 = 128 | C.repeated int32 "repeated_int32" 51 129 | end 130 | -------------------------------------------------------------------------------- /pb/test/test_read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import test_pb2 4 | 5 | def from_bytes(msg, filename): 6 | with open(filename, 'rb') as fd: 7 | return msg.FromString(fd.read()) 8 | 9 | def main(): 10 | small = from_bytes(test_pb2.Small, 11 | 'small.ocaml.serialized') 12 | assert small.small_s == 'abc' 13 | assert small.small_i == 17 14 | 15 | two = from_bytes(test_pb2.TwoString, 16 | 'twostring.ocaml.serialized') 17 | assert two.two_s == 'abc' 18 | assert two.two_b == 'def' 19 | 20 | c = from_bytes(test_pb2.Comprehensive, 21 | 'comprehensive.ocaml.serialized') 22 | assert list(c.repeated_uint32) == [1,2] 23 | assert c.required_int32 == 3 24 | s1 = c.required_Small 25 | assert s1.small_s == 'abc' 26 | assert s1.small_i == 17 27 | assert c.required_double == 4.1 28 | assert c.optional_sfixed32 == 5 29 | assert c.optional_fixed32 == 6 30 | assert list(c.repeated_bytes) == [b'def', b'gh'] 31 | assert list(c.repeated_bool) == [False, True] 32 | assert list(c.repeated_sfixed64) == [7,8,9] 33 | assert c.optional_bool == True 34 | assert c.required_uint32 == 10 35 | assert c.optional_double == 11.2 36 | assert c.required_int64 == 12 37 | assert c.required_uint64 == 13 38 | assert c.required_string == 'rstuvw' 39 | assert c.required_bytes == 'lmnopq' 40 | assert c.optional_bytes == b'rstuv' 41 | assert c.optional_sint64 == 14 42 | assert list(c.repeated_sint64) == [-15,16,17] 43 | assert list(c.repeated_fixed32) == [18,19,20,21] 44 | s1 = c.optional_Small 45 | assert s1.small_s == 'abc' 46 | assert s1.small_i == 17 47 | assert c.optional_int32 == 22 48 | assert c.optional_fixed64 == 23 49 | e1 = c.optional_enum 50 | assert e1 == test_pb2.Enum.Value('one') 51 | assert c.required_float == 24.5 52 | assert c.optional_sfixed64 == 25 53 | assert c.required_sfixed32 == 26 54 | assert c.required_bool == True 55 | assert list(c.repeated_fixed64) == [27,28,29,30,31] 56 | assert c.optional_sint32 == 32 57 | assert list(c.repeated_int64) == [33,34,35,36,37,38,100000000039] 58 | assert c.required_fixed64 == 40 59 | e2 = test_pb2.Enum.Value('two') 60 | assert list(c.repeated_enum) == [e1, e2] 61 | assert c.optional_int64 == 41 62 | assert list(c.repeated_float) == [42.0] 63 | assert list(c.repeated_sint32) == [44, 45, 46, 47, 48, 49] 64 | assert list(c.repeated_uint64) == [50, 51, 52, 53, 54, 55] 65 | [s1, s2] = c.repeated_Small 66 | assert s1.small_s == 'abc' 67 | assert s1.small_i == 17 68 | assert s2.small_s == '' 69 | assert s2.small_i == 100 70 | assert list(c.repeated_double) == [56.3, 57.4, 58.0, 59.1] 71 | assert list(c.repeated_string) == ['w', '', 'yz'] 72 | assert c.required_sfixed64 == 60 73 | assert c.required_sint64 == 61 74 | assert c.optional_string == 'A3' 75 | assert c.optional_uint32 == 62 76 | assert list(c.repeated_sfixed32) == [63, 64, 65, 66, 67, 68] 77 | assert c.optional_float == 69.0 78 | assert c.optional_uint64 == 70 79 | assert c.required_enum == e2 80 | assert c.required_sint32 == 71 81 | assert c.required_fixed32 == 72 82 | assert list(c.repeated_int32) == [73, 74, 75, 76, 77, 78] 83 | 84 | 85 | if __name__ == '__main__': 86 | main() 87 | --------------------------------------------------------------------------------