From e6b6dedb550edbd0e54e212799e42aae2f1a87f1 Mon Sep 17 00:00:00 2001 From: dcodeIO Date: Wed, 5 Apr 2017 18:28:05 +0200 Subject: [PATCH] Docs: Rephrased the Usage section around the concept of valid messages --- README.md | 108 +++++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 29037eb77..6b627678a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ Contents * [Usage](#usage)
A brief introduction to using the toolset. + * [Valid Message](#valid-message) + * [Toolset](#toolset) + + * [Examples](#examples)
A few examples to get you started. @@ -59,39 +63,41 @@ $> npm install protobufjs [--save --save-prefix=~] var protobuf = require("protobufjs"); ``` +**Note** that this library's versioning scheme is not semver-compatible for historical reasons. For guaranteed backward compatibility, always depend on `~6.A.B` instead of `^6.A.B` (hence the `--save-prefix` above). + ### Browsers Development: + ``` ``` Production: + ``` ``` -**NOTE:** Remember to replace the version tag with the exact [release](https://github.com/dcodeIO/protobuf.js/tags) your project depends upon. +**Remember** to replace the version tag with the exact [release](https://github.com/dcodeIO/protobuf.js/tags) your project depends upon. -The `protobuf` namespace will always be available globally / also supports AMD loaders. +The library supports CommonJS and AMD loaders and also exports globally as `protobuf`. ### Distributions -The library supports both reflection-based and code-based use cases: +Where bundle size is a factor, there are additional stripped-down versions of the [full library][dist-full] (~19kb gzipped) available that exclude certain functionality: -1. Parsing protocol buffer definitions (.proto files) to reflection -2. Loading JSON descriptors to reflection -3. Generating static code without any reflection features +* When working with JSON descriptors (i.e. generated by [pbjs](#pbjs-for-javascript)) and/or reflection only, see the [light library][dist-light] (~16kb gzipped) that excludes the parser. CommonJS entry point is: -Where bundle size is a factor, there is a suitable distribution for each of these: + ```js + var protobuf = require("protobufjs/light"); + ``` -| | Gzipped | Downloads | How to require | Description -|---------|---------|------------------------------|---------------------------------|------------- -| full | 18.5kb | [dist][dist-full] | `require("protobufjs")` | All features. Works with everything. -| light | 15.5kb | [dist/light][dist-light] | `require("protobufjs/light")` | All features except tokenizer, parser and bundled common types. Works with JSON definitions, pure reflection and static code. -| minimal | 6.0kb+ | [dist/minimal][dist-minimal] | `require("protobufjs/minimal")` | Just enough to run static code. No reflection. +* When working with statically generated code only, see the [minimal library][dist-minimal] (~6.5kb gzipped) that also excludes reflection. CommonJS entry point is: -In case of doubt it is safe to just use the full library. + ```js + var protobuf = require("protobufjs/minimal"); + ``` [dist-full]: https://github.com/dcodeIO/protobuf.js/tree/master/dist [dist-light]: https://github.com/dcodeIO/protobuf.js/tree/master/dist/light @@ -100,12 +106,43 @@ In case of doubt it is safe to just use the full library. Usage ----- -Each message type provides a set of methods with each method doing just one thing. This avoids unnecessary operations where [performance](#performance) is a concern but also forces a user to perform verification explicitly where necessary - for example when dealing with user input. +Because JavaScript is a dynamically typed language, protobuf.js introduces the concept of a **valid message** in order to provide the best possible [performance](#performance): + +### Valid message + +> **A valid message is an object not missing any required fields and exclusively using JS types for its fields (properties) that are understood by the wire format writer.** + +There are two possible types of valid messages and the encoder is able to work with both of these: + +* **Runtime messages** (explicit instances of message classes with default values on their prototype) always (have to) satisfy the requirements of a valid message and +* **Plain JavaScript objects** that just so happen to be composed in a way satisfying the requirements of a valid message as well. + +In a nutshell, the wire format writer understands the following types: + +| Field type | Expected JS type (create, encode) | Naive conversion (fromObject) +|------------|-----------------------------------|------------------------------ +| s-/u-/int32
s-/fixed32 | `number` (32 bit integer) | `value | 0` if signed
`value >>> 0` if unsigned +| s-/u-/int64
s-/fixed64 | `Long`-like (optimal)
`number` (53 bit integer) | `Long.fromValue(value)` with long.js
`parseInt(value, 10)` otherwise +| float
double | `number` | `Number(value)` +| bool | `boolean` | `Boolean(value)` +| string | `string` | `String(value)` +| bytes | `Uint8Array` (optimal)
`Buffer` (optimal under node)
`Array.` (8 bit integers) | `base64.decode(value)` if a `string`
`Object` with non-zero `.length` is assumed to be buffer-like +| enum | `number` (32 bit integer) | Looks up the numeric id if a `string` +| message | Valid message | `Message.fromObject(value)` + +* Explicit `undefined` and `null` are considered as not set if the field is optional. +* Repeated fields are `Array.`. +* Map fields are `Object.` with the key being the string representation of the respective value or an 8 characters long binary hash string for `Long`-likes. +* Types marked as *optimal* provide the best performance because no conversion step (i.e. number to low and high bits or base64 string to buffer) is required. + +### Toolset -Note that **Message** below refers to any message type. See the next section for the definition of a [valid message](#valid-message). +With that in mind and again for performance reasons, each message class provides a distinct set of methods with each method doing just one thing. This avoids unnecessary assertions / operations where performance is a concern but also forces a user to perform verification (of plain JavaScript objects that *might* just so happen to be a valid message) explicitly where necessary - for example when dealing with user input. + +**Note** that `Message` below refers to any message class. * **Message.verify**(message: `Object`): `null|string`
- explicitly performs verification prior to encoding a plain object. Instead of throwing, it returns the error message as a string, if any. + verifies that a **plain JavaScript object** satisfies the requirements of a valid message and thus can be encoded without issues. Instead of throwing, it returns the error message as a string, if any. ```js var payload = "invalid (not an object)"; @@ -115,7 +152,7 @@ Note that **Message** below refers to any message type. See the next section for ``` * **Message.encode**(message: `Message|Object` [, writer: `Writer`]): `Writer`
- is an automatically generated message specific encoder expecting a valid message or plain object. Note that this method does not implicitly verify the message and that it's up to the user to make sure that the data can actually be encoded properly. + encodes a valid message (**runtime message** or valid **plain JavaScript object**). This method does not implicitly verify the message and it's up to the user to make sure that the payload is a valid message. ```js var buffer = AwesomeMessage.encode(message).finish(); @@ -125,7 +162,7 @@ Note that **Message** below refers to any message type. See the next section for works like `Message.encode` but additionally prepends the length of the message as a varint. * **Message.decode**(reader: `Reader|Uint8Array`): `Message`
- is an automatically generated message specific decoder. If required fields are missing, it throws a `util.ProtocolError` with an `instance` property set to the so far decoded message. If the wire format is invalid, it throws an `Error`. The result is a runtime message. + decodes a buffer to a **runtime message**. If required fields are missing, it throws a `util.ProtocolError` with an `instance` property set to the so far decoded message. If the wire format is invalid, it throws an `Error`. ```js try { @@ -143,14 +180,14 @@ Note that **Message** below refers to any message type. See the next section for works like `Message.decode` but additionally reads the length of the message prepended as a varint. * **Message.create**(properties: `Object`): `Message`
- quickly creates a new runtime message from known to be valid properties without any conversion being performed. Where applicable, it is recommended to prefer `Message.create` over `Message.fromObject`. + creates a new **runtime message** from a set of properties that satisfy the requirements of a valid message. Where applicable, it is recommended to prefer `Message.create` over `Message.fromObject` because it doesn't perform possibly redundant conversion. ```js var message = AwesomeMessage.create({ awesomeField: "AwesomeString" }); ``` * **Message.fromObject**(object: `Object`): `Message`
- converts any plain object to a runtime message. Tries to convert whatever is specified (use `Message.verify` before if necessary). + naively converts any non-valid **plain JavaScript object** to a **runtime message**. See the table above for the exact conversion operations performed. ```js var message = AwesomeMessage.fromObject({ awesomeField: 42 }); @@ -158,7 +195,7 @@ Note that **Message** below refers to any message type. See the next section for ``` * **Message.toObject**(message: `Message` [, options: `ConversionOptions`]): `Object`
- converts a runtime message to a plain object. + converts a **runtime message** to an arbitrary **plain JavaScript object** for interoperability with other libraries or storage. The resulting plain JavaScript object *might* still satisfy the requirements of a valid message depending on the actual conversion options specified, but most of the time it does not. ```js var object = AwesomeMessage.toObject(message, { @@ -172,37 +209,10 @@ Note that **Message** below refers to any message type. See the next section for }); ``` - See also: [ConversionOptions](http://dcode.io/protobuf.js/global.html#ConversionOptions) - -In pictures: +For reference, the following diagram aims to display the relationships between the different methods above and the concept of a valid message: Toolset Diagram -### Valid message - -A valid message is an object not missing any required fields and exclusively using JS types for its fields / properties that are understood by the wire format writer. - -* Calling `Message.verify` with any object returns `null` if the object can be encoded as-is and otherwise the error as a string. -* Calling `Message.create` or `Message.encode` must be called with a valid message. -* Calling `Message.fromObject` with any object naively converts all values to the optimal JS type. - -| Field type | Expected JS type (create, encode) | Naive conversion (fromObject) -|------------|-----------------------------------|------------------------------ -| s-/u-/int32
s-/fixed32 | `Number` (32 bit integer) | `value | 0` if signed
`value >>> 0` if unsigned -| s-/u-/int64
s-/fixed64 | `Long`-like (optimal)
`Number` (53 bit integer) | `Long.fromValue(value)` with long.js
`parseInt(value, 10)` otherwise -| float
double | `Number` | `Number(value)` -| bool | `Boolean` | `Boolean(value)` -| string | `String` | `String(value)` -| bytes | `Uint8Array` (optimal)
`Buffer` (optimal under node)
`Array.` (8 bit integers)
`String` (base64) | `base64.decode(value)` if a String
`Object` with non-zero `.length` is kept -| enum | `Number` (32 bit integer) | Looks up the numeric id if a string -| message | Valid message | `Message.fromObject(value)` - -* Explicit `undefined` and `null` are considered as not set when optional. -* Repeated fields are `Array.`. -* Map fields are `Object.` with the key being the string representation of the respective value or an 8 characters long binary hash string for `Long`-likes. -* `String` refers to both objects and values while `Number` refers to values only. -* Types marked as *optimal* provide the best performance because no conversion step (i.e. number to low and high bits or base64 string to buffer) is required. - Examples --------