Skip to content

Commit 6941bcc

Browse files
committed
Add support for proto3 optional scalars
Protobuf 3.15 introduced support for marking scalar fields like uint32 as optional, and all of our tooling appears to support it now. This allows us to use simple optional/null checks in our Rust/ TypeScript code, without having to resort to an inner message. I had to apply a minor patch to protobufjs to get this working with the json-module output; this has also been submitted upstream: protobufjs/protobuf.js#1693 I've modified CardStatsResponse as an example of the new syntax. One thing to note: while the Rust and TypeScript bindings use optional/ null fields, as that is the norm in those languages, Google's Python bindings are not very Pythonic. Referencing an optional field that is missing will yield the default value, and a separate HasField() call is required, eg: ``` >>> from anki.stats_pb2 import CardStatsResponse as R ... msg = R.FromString(b"") ... print(msg.first_review) ... print(msg.HasField("first_review")) 0 False ```
1 parent 55c64e5 commit 6941bcc

File tree

6 files changed

+42
-53
lines changed

6 files changed

+42
-53
lines changed

proto/anki/stats.proto

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ message CardStatsResponse {
3232
string deck = 4;
3333
// Unix timestamps
3434
int64 added = 5;
35-
generic.Int64 first_review = 6;
36-
generic.Int64 latest_review = 7;
37-
generic.Int64 due_date = 8;
38-
generic.Int32 due_position = 9;
35+
optional int64 first_review = 6;
36+
optional int64 latest_review = 7;
37+
optional int64 due_date = 8;
38+
optional int32 due_position = 9;
3939
// days
4040
uint32 interval = 10;
4141
// per mill

proto/protobuf.bzl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,39 +33,39 @@ def setup_protobuf_binary(name):
3333
http_archive,
3434
name = "protoc_bin_macos",
3535
urls = [
36-
"https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-osx-x86_64.zip",
36+
"https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip",
3737
],
38-
sha256 = "699ceee7ef0988ecf72bf1c146dee5d9d89351a19d4093d30ebea3c04008bb8c",
38+
sha256 = "d8b55cf1e887917dd43c447d77bd5bd213faff1e18ac3a176b35558d86f7ffff",
3939
build_file_content = """exports_files(["bin/protoc"])""",
4040
)
4141

4242
maybe(
4343
http_archive,
4444
name = "protoc_bin_linux_x86_64",
4545
urls = [
46-
"https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip",
46+
"https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip",
4747
],
48-
sha256 = "a2900100ef9cda17d9c0bbf6a3c3592e809f9842f2d9f0d50e3fba7f3fc864f0",
48+
sha256 = "058d29255a08f8661c8096c92961f3676218704cbd516d3916ec468e139cbd87",
4949
build_file_content = """exports_files(["bin/protoc"])""",
5050
)
5151

5252
maybe(
5353
http_archive,
5454
name = "protoc_bin_linux_arm64",
5555
urls = [
56-
"https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-aarch_64.zip",
56+
"https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-aarch_64.zip",
5757
],
58-
sha256 = "67db019c10ad0a151373278383e4e9b756dc90c3cea6c1244d5d5bd230af7c1a",
58+
sha256 = "95584939e733bdd6ffb8245616b2071f565cd4c28163b6c21c8f936a9ee20861",
5959
build_file_content = """exports_files(["bin/protoc"])""",
6060
)
6161

6262
maybe(
6363
http_archive,
6464
name = "protoc_bin_windows",
6565
urls = [
66-
"https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-win64.zip",
66+
"https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip",
6767
],
68-
sha256 = "642554ed4dd2dba94e1afddcccdd7d832999cea309299cc5952f13db389894f8",
68+
sha256 = "828d2bdfe410e988cfc46462bcabd34ffdda8cc172867989ec647eadc55b03b5",
6969
build_file_content = """exports_files(["bin/protoc.exe"])""",
7070
)
7171

rslib/src/stats/card.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,8 @@ impl Collection {
3232
note_id: card.note_id.into(),
3333
deck: deck.human_name(),
3434
added: card.id.as_secs().0,
35-
first_review: revlog.first().map(|entry| pb::generic::Int64 {
36-
val: entry.id.as_secs().0,
37-
}),
38-
latest_review: revlog.last().map(|entry| pb::generic::Int64 {
39-
val: entry.id.as_secs().0,
40-
}),
35+
first_review: revlog.first().map(|entry| entry.id.as_secs().0),
36+
latest_review: revlog.last().map(|entry| entry.id.as_secs().0),
4137
due_date,
4238
due_position,
4339
interval: card.interval,
@@ -52,22 +48,17 @@ impl Collection {
5248
})
5349
}
5450

55-
fn due_date_and_position(
56-
&mut self,
57-
card: &Card,
58-
) -> Result<(Option<pb::generic::Int64>, Option<pb::generic::Int32>)> {
51+
fn due_date_and_position(&mut self, card: &Card) -> Result<(Option<i64>, Option<i32>)> {
5952
let due = if card.original_due != 0 {
6053
card.original_due
6154
} else {
6255
card.due
6356
};
6457
Ok(match card.queue {
65-
CardQueue::New => (None, Some(pb::generic::Int32 { val: due })),
58+
CardQueue::New => (None, Some(due)),
6659
CardQueue::Learn => (
67-
Some(pb::generic::Int64 {
68-
val: TimestampSecs::now().0,
69-
}),
70-
card.original_position.map(|u| (u as i32).into()),
60+
Some(TimestampSecs::now().0),
61+
card.original_position.map(|u| u as i32),
7162
),
7263
CardQueue::Review | CardQueue::DayLearn => (
7364
{
@@ -78,10 +69,10 @@ impl Collection {
7869
let days_remaining = due - (self.timing_today()?.days_elapsed as i32);
7970
let mut due = TimestampSecs::now();
8071
due.0 += (days_remaining as i64) * 86_400;
81-
Some(pb::generic::Int64 { val: due.0 })
72+
Some(due.0)
8273
}
8374
},
84-
card.original_position.map(|u| (u as i32).into()),
75+
card.original_position.map(|u| u as i32),
8576
),
8677
_ => (None, None),
8778
})

ts/card-info/CardStats.svelte

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
44
-->
55
<script lang="ts">
66
import * as tr2 from "../lib/ftl";
7-
import { Stats, unwrapOptionalNumber } from "../lib/proto";
7+
import { Stats } from "../lib/proto";
88
import { DAY, timeSpan, Timestamp } from "../lib/time";
99
1010
export let stats: Stats.CardStatsResponse;
@@ -23,33 +23,29 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
2323
2424
statsRows.push({ label: tr2.cardStatsAdded(), value: dateString(stats.added) });
2525
26-
const firstReview = unwrapOptionalNumber(stats.firstReview);
27-
if (firstReview !== undefined) {
26+
if (stats.firstReview != null) {
2827
statsRows.push({
2928
label: tr2.cardStatsFirstReview(),
30-
value: dateString(firstReview),
29+
value: dateString(stats.firstReview),
3130
});
3231
}
33-
const latestReview = unwrapOptionalNumber(stats.latestReview);
34-
if (latestReview !== undefined) {
32+
if (stats.latestReview != null) {
3533
statsRows.push({
3634
label: tr2.cardStatsLatestReview(),
37-
value: dateString(latestReview),
35+
value: dateString(stats.latestReview),
3836
});
3937
}
4038
41-
const dueDate = unwrapOptionalNumber(stats.dueDate);
42-
if (dueDate !== undefined) {
39+
if (stats.dueDate != null) {
4340
statsRows.push({
4441
label: tr2.statisticsDueDate(),
45-
value: dateString(dueDate),
42+
value: dateString(stats.dueDate),
4643
});
4744
}
48-
const duePosition = unwrapOptionalNumber(stats.duePosition);
49-
if (duePosition !== undefined) {
45+
if (stats.duePosition != null) {
5046
statsRows.push({
5147
label: tr2.cardStatsNewCardPosition(),
52-
value: duePosition,
48+
value: stats.duePosition,
5349
});
5450
}
5551

ts/lib/proto.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,3 @@ export const stats = Stats.StatsService.create(serviceCallback as RPCImpl);
7373

7474
export { Tags };
7575
export const tags = Tags.TagsService.create(serviceCallback as RPCImpl);
76-
77-
export function unwrapOptionalNumber(
78-
msg: Generic.IInt64 | Generic.IUInt32 | Generic.IInt32 | null | undefined,
79-
): number | undefined {
80-
if (msg && msg !== null) {
81-
if (msg.val !== null) {
82-
return msg.val;
83-
}
84-
}
85-
return undefined;
86-
}

ts/patches/protobufjs+6.11.2.patch

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
diff --git a/node_modules/protobufjs/src/field.js b/node_modules/protobufjs/src/field.js
2+
index 20c1cd2..3a1395f 100644
3+
--- a/node_modules/protobufjs/src/field.js
4+
+++ b/node_modules/protobufjs/src/field.js
5+
@@ -270,6 +270,8 @@ Field.prototype.resolve = function resolve() {
6+
this.typeDefault = null;
7+
else // instanceof Enum
8+
this.typeDefault = this.resolvedType.values[Object.keys(this.resolvedType.values)[0]]; // first defined
9+
+ } else if (this.options && this.options.proto3_optional) {
10+
+ this.typeDefault = null;
11+
}
12+
13+
// use explicitly set default value if present
114
diff --git a/node_modules/protobufjs/src/root.js b/node_modules/protobufjs/src/root.js
215
index df6f11f..112f9e8 100644
316
--- a/node_modules/protobufjs/src/root.js

0 commit comments

Comments
 (0)