Skip to content

Commit

Permalink
feat: Add required message
Browse files Browse the repository at this point in the history
See in readme **strict required mode for messages**
  • Loading branch information
askuzminov committed Sep 8, 2021
1 parent 27d1e83 commit 087fe9c
Show file tree
Hide file tree
Showing 25 changed files with 3,280 additions and 3,154 deletions.
4,798 changes: 1,716 additions & 3,082 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"grpc:protobufjs": "PROTO_DIR=proto PROTO_OUT=tests/protobufjs ts-node src/generator/protobufjs.ts",
"gprc:generator": "PROTO_DIR=proto PROTO_OUT=tests/proto PROTO_VERSION=test PROTO_DEBUG=true ts-node src/generator/cli.ts",
"gprc:package": "PROTO_DIR=proto PROTO_OUT=tests/proto PROTO_PACKAGE_NAME=abc PROTO_PACKAGE_VERSION=0.1.10 ts-node src/generator/cli.ts",
"lint:prettier": "prettier --write \"@(packages)/**/*.{ts,tsx,js,json,css,md,html,yml}\"",
"lint:prettier": "prettier --write \"@(src|tests)/**/*.{ts,tsx,js,json,css,md,html,yml}\"",
"lint:fix": "npm run eslint:fix && npm run lint:prettier",
"eslint": "eslint '**/*.{js,ts,tsx}'",
"eslint:fix": "npm run eslint -- --fix",
Expand All @@ -68,30 +68,30 @@
"dependencies": {
"@whisklabs/deep-readonly": "~1.0.0",
"@whisklabs/typeguards": "~1.0.0",
"typescript": "~4.1.3"
"typescript": "~4.4.2"
},
"devDependencies": {
"@askuzminov/simple-release": "~1.0.8",
"@types/jest": "^26.0.19",
"@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0",
"eslint": "^7.16.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^30.7.9",
"eslint-plugin-prefer-arrow": "^1.2.2",
"eslint-plugin-prettier": "^3.3.0",
"eslint-plugin-unicorn": "^24.0.0",
"husky": "^4.3.6",
"jest": "~26.6.3",
"jest-junit": "^12.0.0",
"lint-staged": "^10.5.3",
"prettier": "~2.2.1",
"prettier-eslint": "^12.0.0",
"@types/jest": "^27.0.1",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsdoc": "^36.0.8",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unicorn": "^35.0.0",
"husky": "^4.3.8",
"jest": "^27.1.0",
"jest-junit": "^12.2.0",
"lint-staged": "^11.1.2",
"prettier": "~2.3.2",
"prettier-eslint": "^13.0.0",
"protobufjs": "~6.9.0",
"rimraf": "^3.0.2",
"ts-jest": "~26.4.4",
"ts-node": "^9.1.1"
"ts-jest": "~27.0.5",
"ts-node": "^10.2.1"
},
"prettier": {
"arrowParens": "avoid",
Expand Down
51 changes: 51 additions & 0 deletions proto/whisk/api/user/v2/optional.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
syntax = "proto3";

package whisk.api.user.v2;

// Force required mode for messages in file
option message_required = true; // false for disable

message Test {
// Primitive
string id = 1; // required
optional string text = 2; // optional

// Struct
Week current_week = 11; // required
optional Week next_week = 12; // optional
whisk.api.shared.v1.Time time = 13; // required
optional whisk.api.shared.v1.Time time_after = 14; // optional

// Wrappers (legacy)
google.protobuf.StringValue description = 21; // Optional string

// Force override (backward binary compatibility only)
string item = 31 [ (required) = false ]; // optional
google.protobuf.StringValue test = 32 [ (required) = true ]; // required

// Repeated - can't work with optional!
repeated bool array = 41; // required
repeated bool array_2 = 42 [ (required) = false ]; // optional

// Map - can't work with optional!
map<string, bool> map_search = 51; // required
map<string, bool> map_search_2 = 52 [ (required) = false ]; // optional

// Oneof - can't work with optional!
oneof device_description {
Week type = 61; // required
Week custom = 62 [ (required) = false ]; // optional
}
}

message Day {
int32 num = 1;
}

message Week {
// Force required mode in message
option message_required = false; // false for disable

int32 num = 1;
Day day = 2;
}
133 changes: 127 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const error = await generator({
version: '№123', // optional string for version
exclude: /some|regexp/, // optional regexp for exclude files
debug: true, // generate json debug files,
messageRequired: false, // enable strict required mode for messages (default: false)
// If we need package.json, set options packageName and packageVersion
packageName: '@whisklabs/package-one', // generate package.json with name
packageVersion: '0.1.10', // generate package.json with version
Expand Down Expand Up @@ -123,6 +124,7 @@ Required ENV params:
- `PROTO_VERSION` - string of version (default is from npm package lib)
- `PROTO_EXCLUDE` - optional regexp for exclude files
- `PROTO_DEBUG` - true | false - generate json debug files
- `PROTO_MESSAGE_REQUIRED` - true | false - enable [strict required mode for messages](#required-and-optional) (default: false)

If we need package.json, set up both options:

Expand Down Expand Up @@ -533,7 +535,7 @@ This messages auto wrap/unwrap by this lib.
Messages are same as interface in TS
```proto
```protobuf
message TestItem {
string id = 1;
google.protobuf.StringValue description = 2;
Expand Down Expand Up @@ -574,7 +576,7 @@ type OtherItem = {
As TS enum.
`0` fields name with ending `_INVALID` or `_UNSPECIFIED` removed from typings.
```proto
```protobuf
message TestItem {
Type id = 1;
Type force = 2 [ required = true ];
Expand Down Expand Up @@ -615,7 +617,7 @@ const enum Direction {
This is information about server methods, what types of messages are used for request and response.
```proto
```protobuf
message Request {
int32 id = 1;
}
Expand Down Expand Up @@ -652,7 +654,7 @@ type response = ServiceResponse<UserAPI_GetMe>;
This is a grouping of fields where only one of them can be set or read.
```proto
```protobuf
// file path: whisk/api/user/v2/user.proto
message TestOneof {
string id = 1;
Expand Down Expand Up @@ -727,7 +729,7 @@ Other types are **optional**.
You can change this behaveour using option `required` or keyword `optional`.
```proto
```protobuf
message TestOneof {
string id = 1;
string name = 2 [ required = true ];
Expand Down Expand Up @@ -761,11 +763,130 @@ export type whisk_api_user_v2_TestOneof = {
};
```
You can switch on **strict required mode for messages** with `optional` keyword:
- All fields are **required by default**, expect `oneof`.
- To mark a field as optional, you can add the keyword `optional`.
- Although this keyword has no effect on binary compatibility and can be added or removed, clients need to be updated so that they understand how to handle values correctly.
- For backward compatibility, [wrappers](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wrappers.proto) can be used because they are not binary compatible with `optional`.
- If you cannot change the structure in any way, but you need to force the override, then in such a case it is permissible to use `[(required) = true]` or `[(required) = false]`.
1. Global
Work on all files in final
```bash
PROTO_MESSAGE_REQUIRED=true npx @whisklabs/grpc@1
```
```ts
import { generator } from '@whisklabs/grpc/generator';

const error = await generator({
messageRequired: true, // enable strict required mode for messages (default: false)
});
```
2. Local per file
Add option in start of proto file
```protobuf
syntax = "proto3";

package whisk.api.user.v2;

// Force required mode for messages in file
option message_required = true; // false for disable

message Test {}
```
3. Local per message
```protobuf
message Day {
int32 num = 1;
}

message Week {
// Force required mode in message
option message_required = true; // false for disable

int32 num = 1;
Day day = 2;
}
```
Example:
```protobuf
message Test {
// Primitive
string id = 1; // required
optional string text = 2; // optional

// Messages
Week current_week = 11; // required
optional Week next_week = 12; // optional
whisk.api.shared.v1.Time time = 13; // required
optional whisk.api.shared.v1.Time time_after = 14; // optional

// Wrappers (legacy)
google.protobuf.StringValue description = 21; // optional

// Force override (backward binary compatibility only)
string item = 31 [ (required) = false ]; // optional
google.protobuf.StringValue test = 32 [ (required) = true ]; // required

// Repeated - can't work with optional!
repeated bool array = 41; // required
repeated bool array_2 = 42 [ (required) = false ]; // optional

// Map - can't work with optional!
map<string, bool> map_search = 51; // required
map<string, bool> map_search_2 = 52 [ (required) = false ]; // optional

// Oneof - can't work with optional!
oneof device_description {
DeviceType device_type = 61; // required
DeviceType custom_device = 62 [ (required) = false ]; // optional
}
}

message Week {
int32 num = 1;
}
```
Result:
```ts
type Test = {
id: string;
text?: string;
currentWeek: Week;
nextWeek?: Week;
time: whisk_api_shared_v1_Time;
timeAfter?: whisk_api_shared_v1_Time;
description: string;
item?: string;
test: string;
array: boolean[];
array_2?: boolean[];
mapSearch: Record<string, boolean>;
mapSearch_2?: Record<string, boolean>;
deviceDescription?:
| { oneof: 'deviceType'; value: Device_DeviceType }
| { oneof: 'customDevice'; value?: Device_DeviceType };
};
```
### Deprecated
For obsolete fields, you can mark them as `deprecated`.
```proto
```protobuf
message TestOneof {
string id = 1 [ deprecated = true ];
google.protobuf.StringValue description = 10 [ required = true, deprecated = true ];
Expand Down
4 changes: 3 additions & 1 deletion src/generator/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/* eslint-disable camelcase */

import { isText } from '@whisklabs/typeguards';
import { isPresent, isText } from '@whisklabs/typeguards';
import { existsSync } from 'fs';
import { join } from 'path';

Expand All @@ -18,6 +18,7 @@ const {
PROTO_PACKAGE_VERSION,
PROTO_PACKAGE_URL,
PROTO_PACKAGE_REGISTRY,
PROTO_MESSAGE_REQUIRED,
npm_package_devDependencies__whisklabs_grpc,
npm_package_dependencies__whisklabs_grpc,
} = process.env;
Expand All @@ -38,6 +39,7 @@ generator({
? PROTO_VERSION
: findRoot() ?? npm_package_devDependencies__whisklabs_grpc ?? npm_package_dependencies__whisklabs_grpc,
exclude: isText(PROTO_EXCLUDE) ? new RegExp(PROTO_EXCLUDE) : undefined,
messageRequired: isPresent(PROTO_MESSAGE_REQUIRED),
debug: PROTO_DEBUG === 'true',
packageName: PROTO_PACKAGE_NAME,
packageVersion: PROTO_PACKAGE_VERSION,
Expand Down
2 changes: 2 additions & 0 deletions src/generator/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ export const GOOGLE_WRAPPERS: Record<string, string> = {
'google.protobuf.StringValue': 'string',
'google.protobuf.BytesValue': 'bytes',
};

export const OPTION_MESSAGE_REQUIRED = 'message_required';
4 changes: 3 additions & 1 deletion src/generator/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ export const pathField = (field: string, base: string, out: MakeOuts, parent?: s
return safeString(`${base}_${field}`);
};

export const isRequiredField = (field: Parser.Field) =>
export const isRequiredField = (field: Parser.Field, optional?: boolean) =>
isPresent(field.options.required)
? field.options.required !== false
: field.optional
? false
: optional === true
? true
: field.required || field.repeated || field.type === 'map' || isString(TYPES[field.type]);

export const isEnumField = (field: Parser.Field, base: string, out: MakeOuts, parent?: string) =>
Expand Down
1 change: 1 addition & 0 deletions src/generator/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Config {
version?: string;
exclude?: RegExp;
debug?: boolean;
messageRequired?: boolean;
packageName?: string;
packageVersion?: string;
packageUrl?: string;
Expand Down

0 comments on commit 087fe9c

Please sign in to comment.