fix: route shoes try-on to the YouCam v2.0 feature - #47
Merged
Conversation
Selecting Shoes in TRY IT failed with a raw serializer message shown to
the user: "Field 'data' is required for type with serial name
'...YouCamFileResponse', but it was missing at path: $".
Perfect Corp's AI Shoes try-on lives in the v2.0 family, not v1.0. The
v1.0 endpoint returns a legacy `result`-wrapped body, which none of the
response DTOs can decode. Both features now share one API_VERSION, so
the Feature(version, type) holder collapses to a plain feature name.
Two problems the version fix exposed:
- The shoes task takes `gender` and `style` instead of
`garment_category`. Gender is pinned to "male" by product decision
(Worn is an app for men) and style to "random"; both are constants,
so nothing reaches the UI. `explicitNulls = false` keeps the fields
that don't apply out of the request body rather than sending them as
nulls, which shoes was previously doing for `garment_category`.
- No decodeFromString call was guarded, so a contract mismatch bypassed
the friendly-message mapping in ensureSuccess and leaked an internal
class name into the UI. A decode() helper now maps it to an
actionable message and logs the raw body.
The existing tests could not have caught this: all four used TOP, and
the MockEngine matched on `path.contains("/file/")` without inspecting
the version segment. It now matches full paths, so a routing regression
404s and fails the test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both files documented shoes as v1.0/shoes taking no garment category.
Shoes is v2.0/shoes and sends a fixed gender ("male", by product
decision) and style ("random") instead.
Co-Authored-By: Claude Opus 5 (1M context) <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.
Summary
Fixes #45. Selecting Shoes in TRY IT and tapping "See it on me" failed with an internal serializer message shown verbatim to the user:
YouCamApiClient.feature()routed shoes to/s2s/v1.0/..., but Perfect Corp's AI Shoes try-on lives in the v2.0 family. The v1.0 endpoint returns a legacy"result"-wrapped body, so decoding it asYouCamFileResponse— whose payload sits under"data"— failed at the root. The DTOs were reverse-engineered againstcloth-v3only, and shoes was never exercised end to end.Fixing the version exposed two more problems: the shoes task takes different parameters (
gender/style, nogarment_category), and nodecodeFromStringcall was guarded, which is why an internal class name reached the UI instead of the friendly messages every other failure path produces.No Compose or SwiftUI changes and no new strings — gender and style are constants, so nothing reaches the UI layer.
Changes
API_VERSION, so theFeature(version, type)holder collapses to a plain feature name (cloth-v3/shoes).genderis pinned to"male"— Worn is an app for men, so this is a product decision rather than a parameter — andstyleto"random". AddedexplicitNulls = falseso the fields that don't apply are omitted from the request body instead of sent as nulls (shoes was previously sending"garment_category": null).decode()helper wraps all four response decodes, mapping a contract mismatch to "YouCam returned an unexpected response." and logging the raw body so it stays debuggable.MockEnginematchedpath.contains("/file/")and would have accepted any version — a shoes test written against it would have passed while production failed. It now matches full paths and 404s otherwise.README.mdandARCHITECTURE.mdboth still documented thev1.0/shoesrouting.Test plan
Verified here:
./gradlew :shared:allTests— 124 pass, including three new cases: shoes happy path asserting"gender":"male"/"style":"random"/ nogarment_category; the inverse for clothes (guards theexplicitNullschange in both directions); and a regression test that a{"result":{}}body yields a message with nocom.github.wornin it.API_VERSIONback tov1.0fails 3 tests../gradlew detektclean,./gradlew :composeApp:assembleDebugbuilds.Still needs a real device + YouCam credentials:
[YouCam]: expectupload: creating slot ... at /s2s/v2.0/file/shoes, thenstatus=running→status=success.explicitNulls = falsedidn't regresscloth-v3.Not verified from CI
No live call to Perfect Corp was made; the v2.0 shoes contract comes from their docs, not from an observed response. Their docs are also inconsistent about whether the upload path is per-feature (
/s2s/v2.0/file/shoes) or shared (/s2s/v2.0/file). This PR uses per-feature, since that's what already works forcloth-v3. Ifupload/create404s on device, drop the suffix on the file step only — task and poll stay per-feature either way.Checklist
./gradlew detektpassesREADME.mdandARCHITECTURE.mdboth documented the old routingNote
Pre-existing and left alone: the client's user-facing error strings — including the new decode message — are hardcoded English in shared code rather than pulled from
strings.xml/Localizable.strings. That contradicts the project's localization rule but spans the whole file, so it belongs in its own change. It does mean pt-BR users see English for these errors.🤖 Generated with Claude Code