fix(aso): omit null attributes in beta app review submit body (#31)

Apple's betaAppReviewSubmissions endpoint 422s (ENTITY_UNPROCESSABLE) when data.attributes is present but null. The create body built its POST with the generic CreateBody/CreateData whose attributes: () serialized to "attributes": null. Introduce dedicated local CreateBody/CreateData/CreateRelationships carrying only the build relationship (no attributes field), mirroring the review_submission.rs pattern. Replace the misleading test that asserted the buggy null with one asserting attributes is absent. Co-authored-by: siGit Code v1.6.7-acp <noreply@sigit.si>

Seto Elkahfi committed Oct 12, 2026 at 01:20 UTC 592bfc7e7fc6bd01884622a769ccb3ffd81d45ce
1 file changed +40 -12
crates/aso/src/beta_app_review_submission.rs
+40 -12
@@ -14,9 +14,7 @@
14 use async_trait::async_trait;
15 use reqwest::Method;
16 use serde::{Deserialize, Serialize};
17 -use smbcloud_ascapi_core::jsonapi::{
18 - CreateBody, CreateData, Document, ListDocument, Resource, ResourceId, ToOne,
19 -};
17 +use smbcloud_ascapi_core::jsonapi::{Document, ListDocument, Resource, ResourceId, ToOne};
18 use smbcloud_ascapi_core::Client;
19 use smbcloud_ascapi_core::Result;
20
@@ -51,6 +49,23 @@ pub type BetaAppReviewSubmission = Resource<BetaAppReviewSubmissionAttributes>;
49
50 // ── internal create helper ────────────────────────────────────────────────────
51
52 +/// A beta app review submission carries only a `build` relationship. Apple's
53 +/// endpoint rejects a request whose `data.attributes` is present but null
54 +/// (422 `ENTITY_UNPROCESSABLE`), so this body omits the field entirely rather
55 +/// than reusing [`smbcloud_ascapi_core::jsonapi::CreateBody`] with `()`
56 +/// attributes.
57 +#[derive(Debug, Clone, Serialize)]
58 +struct CreateBody {
59 + data: CreateData,
60 +}
61 +
62 +#[derive(Debug, Clone, Serialize)]
63 +struct CreateData {
64 + #[serde(rename = "type")]
65 + resource_type: &'static str,
66 + relationships: CreateRelationships,
67 +}
68 +
69 #[derive(Debug, Clone, Serialize)]
70 struct CreateRelationships {
71 build: ToOne,
@@ -99,15 +114,14 @@ impl BetaAppReviewSubmissionsApi for Client {
114 let body = CreateBody {
115 data: CreateData {
116 resource_type: RESOURCE_TYPE,
102 - attributes: (),
103 - relationships: Some(CreateRelationships {
117 + relationships: CreateRelationships {
118 build: ToOne {
119 data: ResourceId {
120 resource_type: crate::build::RESOURCE_TYPE,
121 id: build_id.to_string(),
122 },
123 },
110 - }),
124 + },
125 },
126 };
127 let doc: Document<BetaAppReviewSubmissionAttributes> = self
@@ -196,17 +210,31 @@ mod tests {
210 }
211
212 #[test]
199 - fn create_body_attributes_unit_serializes() {
200 - // The create body has no attributes — verify `()` round-trips cleanly.
213 + fn create_body_omits_attributes() {
214 + // Apple's endpoint 422s on a null `data.attributes`; the body must omit
215 + // the field entirely and carry only the `build` relationship.
216 let body = CreateBody {
217 data: CreateData {
218 resource_type: RESOURCE_TYPE,
204 - attributes: (),
205 - relationships: None::<()>,
219 + relationships: CreateRelationships {
220 + build: ToOne {
221 + data: ResourceId {
222 + resource_type: crate::build::RESOURCE_TYPE,
223 + id: "BUILD_ID".to_string(),
224 + },
225 + },
226 + },
227 },
228 };
229 let v = serde_json::to_value(&body).unwrap();
209 - // `()` serialises as `null`; the key is present in the object.
210 - assert!(v["data"]["attributes"].is_null());
230 + assert!(
231 + v["data"].get("attributes").is_none(),
232 + "attributes must be omitted, not null: {v}"
233 + );
234 + assert_eq!(v["data"]["type"], RESOURCE_TYPE);
235 + assert_eq!(
236 + v["data"]["relationships"]["build"]["data"]["id"],
237 + "BUILD_ID"
238 + );
239 }
240 }