Repository navigation
ENGL-663: Catch up the Java SDK with the public API - #24
kavanpancholi wants to merge 9 commits into
Conversation
[High risk] The risk rests on the query-value URL encoding, which changes every query parameter across the SDK and breaks callers who pre-encoded values, and on the automation parser change, which alters trigger ids and fields that existing callers read. Both are disclosed in the description and the review's account, and a revert undoes them. FindingsEarlier findings (3 dismissed)
SummaryBrings the Java SDK in line with the public API The PR adds builders and endpoints for subscribers, groups, segments, fields and automations, plus automation triggers, steps, emails, templates and reports. It also fixes the segment What's good
Review context · 0/5 commands
Reviews (4) · Last reviewed commit: "Fall back to generic steps and condition..." · claude-sonnet-5-5 |
|
@mailerlite-kritika review again |
Why?
The Java SDK was behind the public API. It could read automations but not build, change or control them. It also had the known bugs from ENGL-663: segment subscribers sent
limigandafter, the list builders had nopage(), and the automation parser returned null for several trigger, step and action types. This catches it up with every subscribers, groups, segments, fields and automations endpoint on the card.Changes
limit(it waslimig) and takecursor(). The oldafter()is kept and still sendsafter.page().GenericAutomationTriggerandGenericAutomationStep, with the full JSON indata. That coverssubscriber_joins_segment,manual,abandoned_checkout,purchase_frequency,webhooksteps and thesend_internal_email_notificationaction. Steps and triggers parse to the same typed classes (EmailStep,DelayStepand so on) on every endpoint.+in an email reached the API as a space.completedSubscribersCountandsubscribersInQueueCountwere always 0 (missing@SerializedName).split_percentages, which the API sends as objects.postDeserialisewas misspelled and never called. Each class now overridespostDeserialize(), and the old name stays as a deprecated delegate. ISO dates are parsed too.cursor,sort,include, dates,with_fields,with_groups), subscriber create (resubscribe,double_optin, notes), group subscribers (include), segment list (sort,with_filter), segment update (filter), automation list (sort,with_steps), automation activity (sort,with_subscribers).subscribers().forget(),subscribers().activityLog(),groups().importBuilder(), and automationbuilder().create()/update(),delete,copy,enable,disable,test,addSubscribers,readdand the three activity actions. Newautomations().triggers(id),steps(id),emails(id),templates(),selectTemplate()andreports(id).deleted_at,forget_at,groups,location,timezone, fieldis_default, segmentfilter, automation template and build fields, import status counts.AutomationsRequestTest,SubscribersRequestTest,GroupsRequestTest,SegmentsRequestTest,FieldsRequestTest) use a mock HTTP client and assert the HTTP method, path, query and body against the OpenAPI document. Parser tests cover every typed and unknown trigger and step type.No existing method signature changed and nothing was removed.
MailerLite.javais untouched.One behaviour change to note for the release: query values are now URL-encoded. A caller who encoded values by hand to work around the old bug now sends them encoded twice.
Endpoint checklist
"Live call" means the call ran once against a local API on a test account, and the effect was read back with a separate request.
Subscribers
POST /subscriberssubscribers().builder().email().addNote().resubscribe().create()PUT /subscribers/{subscriber}subscribers().builder().addField().update(id)GET /subscribers/{subscriber}subscribers().retriever().getSingle(id)GET /subscriberssubscribers().retriever().filter().include().sort().limit().cursor().withFields().withGroups().get()GET /subscribers (with_groups)subscribers().retriever().withGroups(true).get()with_groupsin the current version (useinclude=groups)GET /subscribers/{subscriber}/activity-logsubscribers().activityLog(id).logName().limit().page().get()GET /subscribers/import/{import}subscribers().imports().getSingle(id)POST /subscribers/{subscriber}/forgetsubscribers().forget(id)DELETE /subscribers/{subscriber}subscribers().delete(id)Groups
POST /groupsgroups().builder().create(name)GET /groupsgroups().retriever().nameFilter().page().limit().sort().get()PUT /groups/{group}groups().builder().update(id, name)POST /subscribers/{subscriber}/groups/{group}groups().subscribers(group).assign(sub)DELETE /subscribers/{subscriber}/groups/{group}groups().subscribers(group).unassign(sub)POST /groups/{group}/import-subscribersgroups().importBuilder(group).addSubscriber().resubscribe().triggerAutomations().start()GET /groups/{group}/subscribersgroups().subscribers(group).include().limit().cursor().statusFilter().get()DELETE /groups/{group}groups().delete(id)Segments
GET /segmentssegments().retriever().sort().page().limit().withFilter().get()PUT /segments/{segment}segments().update(id, name, filter)GET /segments/{segment}/subscriberssegments().subscribers(id).limit(1).cursor(next).statusFilter().get()GET /segments/{segment}/subscribers (old after param)segments().subscribers(id).limit(1).after(id).get()DELETE /segments/{segment}segments().delete(id)Fields
POST /fieldsfields().builder().create(name, type)GET /fieldsfields().retriever().filter(keyword).filter(type).page().limit().sort().get()PUT /fields/{field}fields().builder().update(id, name)DELETE /fields/{field}fields().delete(id)Automations
POST /automationsautomations().builder().name().create()PUT /automations/{automation}automations().builder().name().triggerData().update(a)POST /automations/{automation}/testautomations().test(a, email)POST /automations/{automation}/enableautomations().enable(a)POST /automations/{automation}/add-subscribersautomations().addSubscribers(a, emails, groups, stepId)GET /automations/{automation}/activityautomations().subscriberActivityRetriever().filter(status).filter(search).sort().limit().page().get(a)POST /automations/{automation}/activity/add-to-groupautomations().addActivityToGroups(a, groups, filter)POST /automations/{automation}/activity/skip-delayautomations().skipActivityDelay(a, filter)POST /automations/{automation}/activity/cancel-automationautomations().cancelActivity(a, filter)POST /automations/{automation}/readdautomations().readd(a, to, stepId, filter)POST /automations/{automation}/copyautomations().copy(a)POST /automations/{automation}/disableautomations().disable(a)GET /automations/{automation} (with an A/B step)automations().retriever().getSingle(a)DELETE /automations/{automation}automations().delete(a)GET /automationsautomations().retriever().filter().page().limit().sort().withSteps().get()Automation triggers
POST /automations/{automation}/triggersautomations().triggers(a).create(type, data)GET /automations/{automation}/triggersautomations().triggers(a).get()GET /automations/{automation}/triggers/{trigger}automations().triggers(a).getSingle(t)PUT /automations/{automation}/triggers/{trigger}automations().triggers(a).update(t, data)DELETE /automations/{automation}/triggers/{trigger}automations().triggers(a).delete(t)Automation steps
POST /automations/{automation}/stepsautomations().steps(a).builder().type().parentId().data().create()GET /automations/{automation}/steps/indexautomations().steps(a).get()GET /automations/{automation}/steps/{step}automations().steps(a).getSingle(s)PUT /automations/{automation}/steps/{step}automations().steps(a).builder().data().rescheduleWaitingSubscribers().update(s)POST /automations/{automation}/steps/{step}/copyautomations().steps(a).builder().parentId().copy(s)POST /automations/{automation}/steps/reorderautomations().steps(a).reorder(dragged, newParent, branch, branchForChild)DELETE /automations/{automation}/steps/{step}automations().steps(a).delete(s)PUT /automations/{automation}/steps/{step}/select-ab-test-winnerautomations().steps(a).selectAbTestWinner(s, winner)Automation emails
PUT /automations/{automation}/emails/{email}/contentautomations().emails(a).updateContent(e, html)GET /automations/{automation}/emails/{email}/contentautomations().emails(a).getContent(e)GET /automations/{automation}/emails/{email}/report-statsautomations().emails(a).startDate().endDate().reportStats(e)GET /automations/{automation}/emails/{email}/unsubscribe-reasonsautomations().emails(a).unsubscribeReasons(e)Automation templates
GET /automation-templatesautomations().templates().filter(type).limit().page().get()POST /automations/select-template/{automationTemplate}automations().selectTemplate(t)Automation reports
GET /automations/{automation}/steps/run-countsautomations().reports(a).stepRunCounts()GET /automations/{automation}/steps/run-counts/{step}automations().reports(a).stepRunCount(s)GET /automations/{automation}/triggers/run-countsautomations().reports(a).triggerRunCounts()GET /automations/{automation}/triggers/run-counts/{trigger}automations().reports(a).triggerRunCount(t)GET /automations/{automation}/ab-steps-statsautomations().reports(a).abStepsStats()GET /automations/{automation}/activity/steps/{step}/subscribersautomations().reports(a).stepSubscribers(s).status().sort().filter().limit().page().get()GET /automations/{automation}/activity/triggers/{trigger}/subscribersautomations().reports(a).triggerSubscribers(t).status().get()GET /automations/{automation}/monthly-totals/emailautomations().reports(a).startDate().endDate().monthlyEmailTotals()GET /automations/{automation}/monthly-totals/ecommerceautomations().reports(a).monthlyEcommerceTotals()GET /automations/{automation}/order-activityautomations().reports(a).orderActivity().timeFrame().limit().page().get()GET /automations/{automation}/emails/{email}/ecommerce-overview-statsautomations().emails(a).ecommerceOverviewStats(e)GET /automations/{automation}/ab-steps-stats/{step}automations().reports(a).abStepStats(s)Risks
Low to medium risk. Most of the change is new code. The parts that change existing behaviour:
idand fields change from the automation's values to the trigger's real values.Performance impact
None worth noting. Gson instances are shared, not built per request.
Security impact
No security impact. No auth or transport change.
How to QA
No QA needed on the app. This is a library change, and nothing in the MailerLite app changes. Reviewers can run
mvn test.pom.xmlsetstestFailureIgnore, so read the surefire reports: 112 tests, 0 failures, 0 errors.Verified before review: 66 of 72 live calls passed. The other 6:
with_groups: the SDK sends it, but the current API version ignores it.include=groupsworks and passes.How to release
Standard release. No hand-written CHANGELOG in this repo. The open simple-editor PR #23 and e-commerce PR #22 both touch
MailerLite.java. This PR does not, so they should not conflict there.Rollback strategy
Primary rollback method: revert
Code changes
Database migrations
Data modifications
External dependencies
Configuration changes:
Screenshots, recordings
N/A, library change.
I used AI to generate parts of this PR
Yes
Docs
The PR adheres to Pull Request guidelines.
See mandatory MailerLite PR rules and flow for more information.