Repository navigation
fix: S3 bulk jobs drifted from the shared required-field helper - #18
Merged
Merged
Conversation
validateS3File and scoreS3File each built their own form instead of going
through bulkFileJobFields, and both had drifted from the four jobs that do.
The live API requires return_url on every one of these endpoints — POST
/validateS3file without it answers 400 {"error_message":"Provided return URL
is not permitted: None"} — but scoreS3File had no returnUrl field at all, so
a required parameter was unreachable from the public API and every call
failed, and validateS3File sent it only when it happened to be non-empty.
Both now build their shared fields through the helper and add only the extra
fields they alone take, so the next change to what the API requires is made
in one place. They also stop sending the undocumented singular
remove_duplicate alongside remove_duplicates: that field belongs to the bulk
/v2/sendfile upload, where SendFileOptions still carries it.
exportS3File hard-rejected an empty export_bucket although the docs make it
optional and exportSFTPFile already treats export_path that way; it now takes
an optional bucket and omits the field when unset.
sendEmailFinderFile validated only domain_column, so an upload with no name
column reached the API and was refused there; one of first_name_column and
full_name_column is now required before the request.
bulkApiRootUrl, bulkApiBaseUrl and bulkApiScoringBaseUrl were const with no
setter, so operations 11-27 and 30-40 could only ever reach production and
none of them could be pointed at a test host. They now have setters in the
same style as setListEvaluatorBaseUrl and setInboxPlacementBaseUrl.
ZeroBounceWarmup::initialize rejected a bad key or a non-https base URL by
leaving the client un-ready with nothing to read back, and warmupReady then
guessed at the reason. initialize now records why it refused; isReady() and
getInitializeError() expose it, and the error callback repeats that reason
rather than a guess. The https requirement stands: the Warmup key travels in
a request header, so a plain-http base URL would put it on the wire in clear.
Also corrects a stale comment claiming the Inbox Placement Tester key goes on
the query string; the code 30 lines below has used a Bearer header for a while.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
validateS3File(op 31) andscoreS3File(op 32) each built their own form instead of going throughbulkFileJobFields, and both had drifted from the four S3/SFTP jobs that use it. That duplication is the bug:return_urlon these endpoints.POST /validateS3filewithout it answers400 {"error_message":"Provided return URL is not permitted: None"}.ScoreS3FileOptionshad noreturnUrlfield at all, so a required parameter was unreachable from the public API and everyscoreS3Filecall failed;validateS3Filesent it only when it happened to be non-empty.has_header_rowwas sent only when set on these two, while the other four always send it.remove_duplicatealongsideremove_duplicates.remove_duplicatebelongs to the bulk/v2/sendfileupload, whereSendFileOptionsstill carries it.What changed
Op 31 / 32 —
validateS3File,scoreS3FileBoth now build their shared fields through
bulkFileJobFieldsand append only the extra fields they alone take (email_address_column, the name/gender/IP columns andallow_phase_2for validate;email_address_columnfor score).fileNameandreturnUrlare required, blank-rejected before the request.ScoreS3FileOptionsgainsreturnUrl; both option structs loseremoveDuplicateand take a plainbool hasHeaderRow = true, matchingBulkFileJobOptions.Op 35 —
exportS3Fileexport_bucketis optional per the docs, andexportSFTPFilealready treatsexport_paththat way. It now takesstd::optional<std::string>and omits the field when unset; a bucket that was passed but is blank is still an error. Existing call sites passing a string literal keep compiling.Op 20 —
sendEmailFinderFileOnly
domain_columnwas validated, so an upload with no name column reached the API and was refused there. One offirst_name_columnandfull_name_columnis now required up front, matching the Java and Python SDKs' message.Bulk base URLs
bulkApiRootUrl,bulkApiBaseUrlandbulkApiScoringBaseUrlwereconstmembers with no setter, so ops 11-27 and 30-40 could only ever reachbulkapi.zerobounce.net. They now havesetBulkApiRootUrl/setBulkApiBaseUrl/setBulkApiScoringBaseUrl, in the style of the existingsetListEvaluatorBaseUrl/setInboxPlacementBaseUrl. The list evaluator and Inbox Placement Tester are still governed by their own setters.ZeroBounceWarmup::initializeIt rejected a padded key or a non-https base URL by leaving the client un-ready with nothing to read back, and
warmupReadythen guessed at the reason.initializenow records why it refused;isReady()andgetInitializeError()expose it, and the error callback repeats that exact reason. The https requirement stands: the Warmup key travels in anAPI-Keyrequest header, so a plain-http base URL would put it on the wire in the clear — and the offline suite injects aBaseRequestHandler, so a plain-HTTP local mock is not needed to test it.Stale comment
The comment above the Inbox Placement Tester block claimed the key "goes on the query string rather than in the body"; the implementation 30 lines below has used
Authorization: Bearerfor a while. Fixed in bothPriority3.cppandZeroBounce.h.team_id— investigated, left aloneThe hardcoded
AnyTeamId = 1onPUT /sending-accountsandPUT /smtp-credentialsis not a guess. The Python reference SDK sendsteam_id: _ANY_TEAM_ID = 1on both, with the same "required by the schema but always replaced with the key's team" comment, and the Java, Go and C# SDKs all do the same. The C++ code already matches the yardstick, so nothing changed here.Tests
New gtest cases with the mocked HTTP handler, asserting method, full URL, form fields, key placement and response parsing:
OfflineApi.ValidateAndScoreS3FileShareTheBulkFileJobFields— both jobs' full field set, the key in the body and not the query string, noremove_duplicate, and the blank/unsetreturn_urlandfile_namerejections.OfflineApi.AValidateS3FileReturnUrlRejectionIsReadBack— the verbatim live400 {"error_message":"Provided return URL is not permitted: None"}body is read back as a short message with status 400.OfflineApi.ExportS3FileTreatsTheBucketAsOptionalOfflineApi.EmailFinderSendFileRequiresAFirstOrFullNameColumnOfflineApi.TheBulkBaseUrlsCanBePointedAtATestHost— bulk validation, scoring, an SFTP job and the bulk finder follow the setters; the list evaluator does not.OfflineWarmup.InitializeReportsWhyItRejectedTheArgumentsThe opt-in live suite (
-DZEROBOUNCE_BUILD_LIVE_TESTS=ON) was also compiled to confirm it still typechecks. It was not run: that needs real keys and spends credits, and perAGENTS.mdit is a pre-release step for a maintainer.🤖 Generated with Claude Code