| name | yamuse-endpoint |
| description | Use when adding, changing or debugging a Yandex Music API endpoint in the yamuse crate — adding a method, modelling a response, or working out why the API answers 400/401 for a request that looks right. |
adding an endpoint to yamuse
before writing anything
check whether a neighbouring method in the same src/api/<domain>.rs already
calls something close. parameter names and response shapes here are observed, not
documented — copy an established one rather than guessing at a new spelling.
the five steps
-
model the response in src/models/<domain>.rs:
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Thing {
pub some_field: Option<String>,
pub items: Vec<Other>,
}
every scalar is Option, every list is Vec. no exceptions — the API drops
fields without notice. free-form or not-yet-modelled objects become
Option<serde_json::Value>; never drop them.
-
add the method to src/api/<domain>.rs inside impl Client, with the verb
and path on the first doc line:
pub async fn thing(&self, id: impl std::fmt::Display) -> Result<Thing> {
self.get(&format!("/things/{id}"), Params::new()).await
}
-
more than ~3 parameters → a query struct whose Default matches the API's
own defaults, re-exported from src/api/mod.rs and src/lib.rs.
two rules that are easy to miss:
- anything interpolated into the path goes through
params::segment first,
unless it is an i64 or a crate constant. a / in a caller's playlist uuid
would otherwise retarget the request at another endpoint.
- a mutation returns
Result<Mutation>, never Result<bool>. end it with
Client::acknowledged(&value).
-
test it in tests/transport.rs with wiremock, asserting the params
actually sent — not just that it deserialises:
Mock::given(method("GET"))
.and(path("/things/1"))
.and(query_param("some-param", "value"))
.respond_with(envelope(serde_json::json!({ "someField": "x" })))
.expect(1)
.mount(&server)
.await;
plus a survives_an_empty_object case in the model module.
-
update the coverage table in README.md and README_ru.md.
which client helper to call
| the endpoint | the helper |
|---|
GET, typed response | self.get(path, params) |
GET, needs unwrapping first | self.get_value(path, params) |
GET with a per-call header | self.get_with_headers(path, params, headers) |
POST form, typed response | self.post_form(path, form) |
POST form, needs unwrapping | self.post_form_value(path, form) |
POST form + query + headers | self.post_form_value_with(path, &query, form, headers) |
POST json body | self.post_json_value(path, &json) |
POST pre-serialized json (queues) | self.post_raw_json_value(path, body, headers) |
PUT/DELETE json (pins) | self.put_value / self.delete_value |
| batch id lookup | self.get_batch(BatchObject::X, ids, extra) |
| a mutation over an id list | self.mutate_batch(path, key, ids) |
a /users/{uid}/… path | self.resolve_uid(user_id)? first |
| the oauth host | self.post_form_absolute(url, form) |
when the API rejects a request that looks right
work down this list — each has bitten this port:
- 401 on a signed endpoint.
tracks/{id}/lyrics and get-file-info need an
HMAC, not just the token. for get-file-info the codecs/transport query
values must be byte-identical to what was signed, and the base64 digest loses
its last character. see src/sign.rs.
- 400 with a compound id.
"42:1001" is a track-and-album pair. sign and send
only the numeric part — sign::track_id_to_number.
- 404 where a uid was expected.
resolve_uid(None) needs init() to have
run. the crate returns Error::IdMissing rather than letting the API 404.
- the connection drops on
/queues. that endpoint needs a real
application/json body; tagging JSON as form-urlencoded makes it hang up. use
post_raw_json_value.
- a mutation "fails" but actually worked. track like/dislike answers
{"revision": N}, not "ok". Client::acknowledged covers both shapes and
hands the revision back in Mutation; anything else is an Err, so a mutation
method never quietly returns "no".
pageSize vs page-size. the label listings use camelCase, the artist
listings hyphenated. upstream inconsistency; copy whichever the original used.
- empty results from
/landing3. it requires eitherUserId; the sample value
is in src/api/landing.rs.
ynison specifics
frames omit every default value and arrive snake_case while the schema documents
camelCase — state.rs accepts both and defaults everything, and
playback_speed defaults to 1.0. do not "fix" that to 0.0.
the device id must stay stable across reconnects, and liveness is judged by the
keepalive ping/pong, not by frame arrival — a paused player is silent but healthy.
both are explained in CLAUDE.md.