Skip to content

Commit bfd7cd3

Browse files
ggreifclaude
andcommitted
Bump to 0.1.2: strip null-valued optional fields from JSON request bodies
Twitter's /2/tweets (and other strict X endpoints) reject payloads that carry optional fields as explicit null. The generated toJSON always emitted every ?T field, which made valid-looking Motoko calls unable to produce accepted requests. This regen swaps the JSON serialisation dependency from serde@3.5.0 to serde-core@0.1.0 (a fork carrying NatLabs/serde#44) and threads `?{ Candid.defaultOptions with skip_null_fields = true }` through every JSON.toText call in the API layer. Null-valued entries are now omitted from outbound JSON, matching how external schemas read "field absent". Public Motoko API of this client is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dc7aa62 commit bfd7cd3

25 files changed

Lines changed: 438 additions & 72 deletions

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Changelog
2+
3+
All notable changes to this package are documented in this file.
4+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [0.1.2](https://github.com/caffeinelabs/x-client/releases/tag/v0.1.2) — 2026-04-21
8+
9+
### Fixed
10+
11+
- Request bodies no longer emit `"field": null` for optional fields that the
12+
caller left unset. Twitter's `/2/tweets` (and other strict-validation X
13+
endpoints) reject explicit `null` values for typed optionals — the generated
14+
client was therefore unable to produce a valid tweet payload. The null
15+
entries are now omitted entirely, matching how external schemas interpret
16+
"field absent".
17+
18+
### Changed
19+
20+
- Depend on `serde-core@0.1.0` (a fork of `serde@3.5.0` carrying the
21+
`skip_null_fields` JSON option pending [NatLabs/serde#44](https://github.com/NatLabs/serde/pull/44))
22+
instead of `serde@3.5.0`. The public Motoko API of this client is
23+
unchanged.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This Motoko client was generated by the [OpenAPI Generator](https://openapi-gene
88
- Generator version: 7.22.0-SNAPSHOT
99
- Build package: org.openapitools.codegen.languages.MotokoClientCodegen
1010

11+
1112
## Models
1213

1314
- ActivityStreamingResponse

mops.lock

Lines changed: 342 additions & 0 deletions
Large diffs are not rendered by default.

mops.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ moc = "1.4.1"
33

44
[package]
55
name = "x-client"
6-
version = "0.1.1"
6+
version = "0.1.2"
77
description = "Generated Motoko client for X API v2"
88
repository = "https://github.com/caffeinelabs/x-client"
99
license = "Apache-2.0"
1010
files = ["src/Config.mo", "src/Apis/**/*.mo", "src/Models/**/*.mo"]
1111

1212
[dependencies]
1313
core = "2.4.0"
14-
serde = "3.5.0"
14+
serde-core = "0.1.0" # ggreif/serde fork with skip_null_fields (NatLabs/serde#44)
1515
"cbor@4.1.0" = "4.1.0"
16-
"itertools@0.2.2" = "0.2.2" # because for serde
17-
base = "0.16.0" # because serde uses json.mo submodule
16+
"itertools@0.2.2" = "0.2.2" # because for serde-core
17+
base = "0.16.0" # because serde-core uses json.mo submodule
1818
xtended-numbers = "2.3.0"
1919
"sha2@0.1.6" = "0.1.6"

src/Apis/AccountActivityApi.mo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type Error_; JSON = Error_ } "../Models/Error_";
1111
import { type Problem; JSON = Problem } "../Models/Problem";
1212
import { type ReplayJobCreateResponse; JSON = ReplayJobCreateResponse } "../Models/ReplayJobCreateResponse";
@@ -205,7 +205,7 @@ module {
205205
body = do ? {
206206
let jsonValue = body;
207207
let candidBlob = to_candid(jsonValue);
208-
let #ok(jsonText) = JSON.toText(candidBlob, [], null) else throw Error.reject("Failed to serialize to JSON");
208+
let #ok(jsonText) = JSON.toText(candidBlob, [], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
209209
Text.encodeUtf8(jsonText)
210210
};
211211
};

src/Apis/ActivityApi.mo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type ActivityStreamingResponse; JSON = ActivityStreamingResponse } "../Models/ActivityStreamingResponse";
1111
import { type ActivitySubscriptionCreateRequest; JSON = ActivitySubscriptionCreateRequest } "../Models/ActivitySubscriptionCreateRequest";
1212
import { type ActivitySubscriptionCreateResponse; JSON = ActivitySubscriptionCreateResponse } "../Models/ActivitySubscriptionCreateResponse";
@@ -204,7 +204,7 @@ module {
204204
body = do ? {
205205
let jsonValue = ActivitySubscriptionCreateRequest.toJSON(activitySubscriptionCreateRequest);
206206
let candidBlob = to_candid(jsonValue);
207-
let #ok(jsonText) = JSON.toText(candidBlob, ["event_type", "filter", "tag", "webhook_id"], null) else throw Error.reject("Failed to serialize to JSON");
207+
let #ok(jsonText) = JSON.toText(candidBlob, ["event_type", "filter", "tag", "webhook_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
208208
Text.encodeUtf8(jsonText)
209209
};
210210
};
@@ -521,7 +521,7 @@ module {
521521
body = do ? {
522522
let jsonValue = ActivitySubscriptionUpdateRequest.toJSON(activitySubscriptionUpdateRequest);
523523
let candidBlob = to_candid(jsonValue);
524-
let #ok(jsonText) = JSON.toText(candidBlob, ["tag", "webhook_id"], null) else throw Error.reject("Failed to serialize to JSON");
524+
let #ok(jsonText) = JSON.toText(candidBlob, ["tag", "webhook_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
525525
Text.encodeUtf8(jsonText)
526526
};
527527
};

src/Apis/BookmarksApi.mo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type BookmarkAddRequest; JSON = BookmarkAddRequest } "../Models/BookmarkAddRequest";
1111
import { type BookmarkFolderPostsResponse; JSON = BookmarkFolderPostsResponse } "../Models/BookmarkFolderPostsResponse";
1212
import { type BookmarkFoldersResponse; JSON = BookmarkFoldersResponse } "../Models/BookmarkFoldersResponse";
@@ -105,7 +105,7 @@ module {
105105
body = do ? {
106106
let jsonValue = BookmarkAddRequest.toJSON(bookmarkAddRequest);
107107
let candidBlob = to_candid(jsonValue);
108-
let #ok(jsonText) = JSON.toText(candidBlob, ["tweet_id"], null) else throw Error.reject("Failed to serialize to JSON");
108+
let #ok(jsonText) = JSON.toText(candidBlob, ["tweet_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
109109
Text.encodeUtf8(jsonText)
110110
};
111111
};

src/Apis/ChatApi.mo

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type ChatAddGroupMembersRequest; JSON = ChatAddGroupMembersRequest } "../Models/ChatAddGroupMembersRequest";
1111
import { type ChatAddGroupMembersResponse; JSON = ChatAddGroupMembersResponse } "../Models/ChatAddGroupMembersResponse";
1212
import { type ChatAddPublicKeyRequest; JSON = ChatAddPublicKeyRequest } "../Models/ChatAddPublicKeyRequest";
@@ -123,7 +123,7 @@ module {
123123
body = do ? {
124124
let jsonValue = ChatAddGroupMembersRequest.toJSON(chatAddGroupMembersRequest);
125125
let candidBlob = to_candid(jsonValue);
126-
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "conversation_key_version", "conversation_participant_keys", "encrypted_avatar_url", "encrypted_title", "user_ids"], null) else throw Error.reject("Failed to serialize to JSON");
126+
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "conversation_key_version", "conversation_participant_keys", "encrypted_avatar_url", "encrypted_title", "user_ids"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
127127
Text.encodeUtf8(jsonText)
128128
};
129129
};
@@ -232,7 +232,7 @@ module {
232232
body = do ? {
233233
let jsonValue = ChatAddPublicKeyRequest.toJSON(chatAddPublicKeyRequest);
234234
let candidBlob = to_candid(jsonValue);
235-
let #ok(jsonText) = JSON.toText(candidBlob, ["generate_version", "public_key", "version"], null) else throw Error.reject("Failed to serialize to JSON");
235+
let #ok(jsonText) = JSON.toText(candidBlob, ["generate_version", "public_key", "version"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
236236
Text.encodeUtf8(jsonText)
237237
};
238238
};
@@ -441,7 +441,7 @@ module {
441441
body = do ? {
442442
let jsonValue = ChatMediaUploadAppendRequest.toJSON(chatMediaUploadAppendRequest);
443443
let candidBlob = to_candid(jsonValue);
444-
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "media", "media_hash_key", "segment_index"], null) else throw Error.reject("Failed to serialize to JSON");
444+
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "media", "media_hash_key", "segment_index"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
445445
Text.encodeUtf8(jsonText)
446446
};
447447
};
@@ -550,7 +550,7 @@ module {
550550
body = do ? {
551551
let jsonValue = ChatMediaUploadFinalizeRequest.toJSON(chatMediaUploadFinalizeRequest);
552552
let candidBlob = to_candid(jsonValue);
553-
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "media_hash_key", "message_id", "num_parts", "ttl_msec"], null) else throw Error.reject("Failed to serialize to JSON");
553+
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "media_hash_key", "message_id", "num_parts", "ttl_msec"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
554554
Text.encodeUtf8(jsonText)
555555
};
556556
};
@@ -658,7 +658,7 @@ module {
658658
body = do ? {
659659
let jsonValue = ChatMediaUploadInitializeRequest.toJSON(chatMediaUploadInitializeRequest);
660660
let candidBlob = to_candid(jsonValue);
661-
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "total_bytes"], null) else throw Error.reject("Failed to serialize to JSON");
661+
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_id", "total_bytes"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
662662
Text.encodeUtf8(jsonText)
663663
};
664664
};
@@ -766,7 +766,7 @@ module {
766766
body = do ? {
767767
let jsonValue = ChatCreateConversationRequest.toJSON(chatCreateConversationRequest);
768768
let candidBlob = to_candid(jsonValue);
769-
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "base64_encoded_key_rotation", "conversation_id", "conversation_key_version", "conversation_participant_keys", "group_admins", "group_avatar_url", "group_description", "group_members", "group_name", "ttl_msec"], null) else throw Error.reject("Failed to serialize to JSON");
769+
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "base64_encoded_key_rotation", "conversation_id", "conversation_key_version", "conversation_participant_keys", "group_admins", "group_avatar_url", "group_description", "group_members", "group_name", "ttl_msec"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
770770
Text.encodeUtf8(jsonText)
771771
};
772772
};
@@ -1293,7 +1293,7 @@ module {
12931293
body = do ? {
12941294
let jsonValue = ChatInitializeConversationKeysRequest.toJSON(chatInitializeConversationKeysRequest);
12951295
let candidBlob = to_candid(jsonValue);
1296-
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "base64_encoded_key_rotation", "conversation_key_version", "conversation_participant_keys"], null) else throw Error.reject("Failed to serialize to JSON");
1296+
let #ok(jsonText) = JSON.toText(candidBlob, ["action_signatures", "base64_encoded_key_rotation", "conversation_key_version", "conversation_participant_keys"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
12971297
Text.encodeUtf8(jsonText)
12981298
};
12991299
};
@@ -1505,7 +1505,7 @@ module {
15051505
body = do ? {
15061506
let jsonValue = ChatMarkConversationReadRequest.toJSON(chatMarkConversationReadRequest);
15071507
let candidBlob = to_candid(jsonValue);
1508-
let #ok(jsonText) = JSON.toText(candidBlob, ["seen_until_sequence_id"], null) else throw Error.reject("Failed to serialize to JSON");
1508+
let #ok(jsonText) = JSON.toText(candidBlob, ["seen_until_sequence_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
15091509
Text.encodeUtf8(jsonText)
15101510
};
15111511
};
@@ -1614,7 +1614,7 @@ module {
16141614
body = do ? {
16151615
let jsonValue = ChatSendMessageRequest.toJSON(chatSendMessageRequest);
16161616
let candidBlob = to_candid(jsonValue);
1617-
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_token", "encoded_message_create_event", "encoded_message_event_signature", "message_id"], null) else throw Error.reject("Failed to serialize to JSON");
1617+
let #ok(jsonText) = JSON.toText(candidBlob, ["conversation_token", "encoded_message_create_event", "encoded_message_event_signature", "message_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
16181618
Text.encodeUtf8(jsonText)
16191619
};
16201620
};

src/Apis/CommunitiesApi.mo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type Error_; JSON = Error_ } "../Models/Error_";
1111
import { type Get2CommunitiesIdResponse; JSON = Get2CommunitiesIdResponse } "../Models/Get2CommunitiesIdResponse";
1212
import { type Get2CommunitiesSearchResponse; JSON = Get2CommunitiesSearchResponse } "../Models/Get2CommunitiesSearchResponse";

src/Apis/CommunityNotesApi.mo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Blob "mo:core/Blob";
66
import Array "mo:core/Array";
77
import Error "mo:core/Error";
88
import Base64 "mo:core/Base64";
9-
import { JSON } "mo:serde";
9+
import { JSON; Candid } "mo:serde-core";
1010
import { type CreateNoteRequest; JSON = CreateNoteRequest } "../Models/CreateNoteRequest";
1111
import { type CreateNoteResponse; JSON = CreateNoteResponse } "../Models/CreateNoteResponse";
1212
import { type DeleteNoteResponse; JSON = DeleteNoteResponse } "../Models/DeleteNoteResponse";
@@ -107,7 +107,7 @@ module {
107107
body = do ? {
108108
let jsonValue = CreateNoteRequest.toJSON(createNoteRequest);
109109
let candidBlob = to_candid(jsonValue);
110-
let #ok(jsonText) = JSON.toText(candidBlob, ["info", "post_id", "test_mode"], null) else throw Error.reject("Failed to serialize to JSON");
110+
let #ok(jsonText) = JSON.toText(candidBlob, ["info", "post_id", "test_mode"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
111111
Text.encodeUtf8(jsonText)
112112
};
113113
};
@@ -319,7 +319,7 @@ module {
319319
body = do ? {
320320
let jsonValue = EvaluateNoteRequest.toJSON(evaluateNoteRequest);
321321
let candidBlob = to_candid(jsonValue);
322-
let #ok(jsonText) = JSON.toText(candidBlob, ["note_text", "post_id"], null) else throw Error.reject("Failed to serialize to JSON");
322+
let #ok(jsonText) = JSON.toText(candidBlob, ["note_text", "post_id"], ?{ Candid.defaultOptions with skip_null_fields = true }) else throw Error.reject("Failed to serialize to JSON");
323323
Text.encodeUtf8(jsonText)
324324
};
325325
};

0 commit comments

Comments
 (0)