Skip to main content

supabase_client_query/
postgrest_execute.rs

1use reqwest::header::{HeaderMap, HeaderValue};
2use serde::de::DeserializeOwned;
3use serde_json::Value as JsonValue;
4
5use supabase_client_core::{StatusCode, SupabaseError, SupabaseResponse};
6
7use crate::sql::{CountOption, SqlOperation, SqlParts};
8
9/// Execute a PostgREST request and parse the response.
10pub async fn execute_rest<T: DeserializeOwned + Send>(
11    http: &reqwest::Client,
12    method: reqwest::Method,
13    url: &str,
14    mut headers: HeaderMap,
15    body: Option<JsonValue>,
16    api_key: &str,
17    schema: &str,
18    parts: &SqlParts,
19) -> SupabaseResponse<T> {
20    // Add standard headers
21    headers.insert("apikey", HeaderValue::from_str(api_key).unwrap());
22    headers.insert(
23        "Authorization",
24        HeaderValue::from_str(&format!("Bearer {}", api_key)).unwrap(),
25    );
26
27    // Set Accept-Profile / Content-Profile for schema if not already set
28    if parts.schema_override.is_none() && schema != "public" {
29        match parts.operation {
30            SqlOperation::Select => {
31                headers
32                    .entry("Accept-Profile")
33                    .or_insert_with(|| HeaderValue::from_str(schema).unwrap());
34            }
35            _ => {
36                headers
37                    .entry("Content-Profile")
38                    .or_insert_with(|| HeaderValue::from_str(schema).unwrap());
39            }
40        }
41    }
42
43    // Default Accept to JSON if not already set
44    headers
45        .entry("Accept")
46        .or_insert(HeaderValue::from_static("application/json"));
47
48    tracing::debug!(
49        method = %method,
50        url = %url,
51        "Executing PostgREST request"
52    );
53
54    let mut request = http.request(method.clone(), url).headers(headers);
55
56    if let Some(body) = body {
57        request = request.json(&body);
58    }
59
60    let response = match request.send().await {
61        Ok(r) => r,
62        Err(e) => return SupabaseResponse::error(SupabaseError::Http(e.to_string())),
63    };
64
65    let status_code = response.status().as_u16();
66    let resp_headers = response.headers().clone();
67
68    // For HEAD method (head mode), we just need the count from Content-Range
69    if method == reqwest::Method::HEAD || parts.head {
70        let count = parse_count_from_headers(&resp_headers);
71        if status_code >= 200 && status_code < 300 {
72            let mut resp = SupabaseResponse::<T>::ok(Vec::new());
73            if let Some(c) = count {
74                resp.count = Some(c);
75            }
76            return resp;
77        } else {
78            return SupabaseResponse::error(SupabaseError::postgrest(
79                status_code,
80                format!("HEAD request failed with status {}", status_code),
81                None,
82            ));
83        }
84    }
85
86    // Read response body
87    let body_text = match response.text().await {
88        Ok(t) => t,
89        Err(e) => return SupabaseResponse::error(SupabaseError::Http(e.to_string())),
90    };
91
92    // Handle error responses
93    if status_code >= 400 {
94        return parse_error_response(status_code, &body_text);
95    }
96
97    // Handle 204 No Content
98    if status_code == 204 || body_text.is_empty() {
99        let count = parse_count_from_headers(&resp_headers);
100        let mut resp = SupabaseResponse::<T>::no_content();
101        resp.count = count;
102        return resp;
103    }
104
105    // Parse count from Content-Range header
106    let count = parse_count_from_headers(&resp_headers);
107
108    // Parse response based on whether single was requested
109    if parts.single {
110        // PostgREST returns a single object (not array) when Accept: application/vnd.pgrst.object+json
111        match serde_json::from_str::<T>(&body_text) {
112            Ok(item) => {
113                let mut resp = build_response_from_operation(vec![item], parts);
114                if let Some(c) = count {
115                    resp.count = Some(c);
116                }
117                resp
118            }
119            Err(e) => SupabaseResponse::error(SupabaseError::Serialization(format!(
120                "Failed to parse single response: {}",
121                e
122            ))),
123        }
124    } else {
125        // Try to parse as array first (normal case)
126        match serde_json::from_str::<Vec<T>>(&body_text) {
127            Ok(data) => {
128                let mut resp = build_response_from_operation(data, parts);
129                if let Some(c) = count {
130                    resp.count = Some(c);
131                }
132                // Respect maybe_single
133                if parts.maybe_single {
134                    match resp.data.len() {
135                        0 | 1 => {}
136                        n => return SupabaseResponse::error(SupabaseError::MultipleRows(n)),
137                    }
138                }
139                resp
140            }
141            Err(_) => {
142                // Maybe it's a single object (e.g., insert with return=representation)
143                match serde_json::from_str::<T>(&body_text) {
144                    Ok(item) => {
145                        let mut resp = build_response_from_operation(vec![item], parts);
146                        if let Some(c) = count {
147                            resp.count = Some(c);
148                        }
149                        resp
150                    }
151                    Err(_) => {
152                        // Handle scalar responses from PostgREST (e.g., scalar RPC functions
153                        // return bare values like `10` or `"hello"` instead of JSON arrays).
154                        // Wrap the scalar in an object keyed by the function/table name.
155                        match serde_json::from_str::<JsonValue>(&body_text) {
156                            Ok(scalar) if !scalar.is_array() && !scalar.is_object() => {
157                                let wrapped = format!(
158                                    "[{{\"{}\": {}}}]",
159                                    parts.table, body_text
160                                );
161                                match serde_json::from_str::<Vec<T>>(&wrapped) {
162                                    Ok(data) => {
163                                        let mut resp =
164                                            build_response_from_operation(data, parts);
165                                        if let Some(c) = count {
166                                            resp.count = Some(c);
167                                        }
168                                        resp
169                                    }
170                                    Err(e) => SupabaseResponse::error(
171                                        SupabaseError::Serialization(format!(
172                                            "Failed to parse scalar response: {}",
173                                            e
174                                        )),
175                                    ),
176                                }
177                            }
178                            _ => SupabaseResponse::error(SupabaseError::Serialization(
179                                format!(
180                                    "Failed to parse response: {}",
181                                    body_text
182                                ),
183                            )),
184                        }
185                    }
186                }
187            }
188        }
189    }
190}
191
192fn build_response_from_operation<T>(data: Vec<T>, parts: &SqlParts) -> SupabaseResponse<T> {
193    let status = match parts.operation {
194        SqlOperation::Insert | SqlOperation::Upsert => StatusCode::Created,
195        _ => StatusCode::Ok,
196    };
197
198    let count = if parts.count != CountOption::None {
199        Some(data.len() as i64)
200    } else {
201        None
202    };
203
204    SupabaseResponse {
205        data,
206        error: None,
207        count,
208        status,
209    }
210}
211
212fn parse_count_from_headers(headers: &HeaderMap) -> Option<i64> {
213    // PostgREST returns count in Content-Range header: "0-9/100" or "*/100"
214    headers
215        .get("content-range")
216        .and_then(|v| v.to_str().ok())
217        .and_then(|s| {
218            if let Some(slash_pos) = s.rfind('/') {
219                let count_str = &s[slash_pos + 1..];
220                if count_str == "*" {
221                    None
222                } else {
223                    count_str.parse::<i64>().ok()
224                }
225            } else {
226                None
227            }
228        })
229}
230
231fn parse_error_response<T>(status_code: u16, body: &str) -> SupabaseResponse<T> {
232    // PostgREST error format: { "message": "...", "code": "...", "details": "...", "hint": "..." }
233    if let Ok(error_obj) = serde_json::from_str::<JsonValue>(body) {
234        let message = error_obj
235            .get("message")
236            .and_then(|v| v.as_str())
237            .unwrap_or("Unknown error")
238            .to_string();
239        let code = error_obj
240            .get("code")
241            .and_then(|v| v.as_str())
242            .map(|s| s.to_string());
243
244        SupabaseResponse::error(SupabaseError::postgrest(status_code, message, code))
245    } else {
246        SupabaseResponse::error(SupabaseError::postgrest(
247            status_code,
248            body.to_string(),
249            None,
250        ))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use reqwest::header::{HeaderMap, HeaderValue};
258    use serde_json::json;
259    use wiremock::matchers::{method, path};
260    use wiremock::{Mock, MockServer, ResponseTemplate};
261
262    // ---- parse_count_from_headers ----
263
264    #[test]
265    fn test_parse_count_range_format() {
266        let mut headers = HeaderMap::new();
267        headers.insert("content-range", HeaderValue::from_static("0-9/100"));
268        assert_eq!(parse_count_from_headers(&headers), Some(100));
269    }
270
271    #[test]
272    fn test_parse_count_star_range_format() {
273        let mut headers = HeaderMap::new();
274        headers.insert("content-range", HeaderValue::from_static("*/42"));
275        assert_eq!(parse_count_from_headers(&headers), Some(42));
276    }
277
278    #[test]
279    fn test_parse_count_star_count() {
280        let mut headers = HeaderMap::new();
281        headers.insert("content-range", HeaderValue::from_static("0-9/*"));
282        assert_eq!(parse_count_from_headers(&headers), None);
283    }
284
285    #[test]
286    fn test_parse_count_no_header() {
287        let headers = HeaderMap::new();
288        assert_eq!(parse_count_from_headers(&headers), None);
289    }
290
291    #[test]
292    fn test_parse_count_no_slash() {
293        let mut headers = HeaderMap::new();
294        headers.insert("content-range", HeaderValue::from_static("0-9"));
295        assert_eq!(parse_count_from_headers(&headers), None);
296    }
297
298    #[test]
299    fn test_parse_count_invalid_number() {
300        let mut headers = HeaderMap::new();
301        headers.insert("content-range", HeaderValue::from_static("0-9/abc"));
302        assert_eq!(parse_count_from_headers(&headers), None);
303    }
304
305    #[test]
306    fn test_parse_count_large_number() {
307        let mut headers = HeaderMap::new();
308        headers.insert("content-range", HeaderValue::from_static("0-99/1000000"));
309        assert_eq!(parse_count_from_headers(&headers), Some(1_000_000));
310    }
311
312    // ---- parse_error_response ----
313
314    #[test]
315    fn test_parse_error_response_valid_json() {
316        let body = r#"{"message":"Row not found","code":"PGRST116","details":null,"hint":null}"#;
317        let resp: SupabaseResponse<JsonValue> = parse_error_response(404, body);
318        assert!(resp.is_err());
319        match resp.error.as_ref().unwrap() {
320            SupabaseError::PostgRest { status, message, code } => {
321                assert_eq!(*status, 404);
322                assert_eq!(message, "Row not found");
323                assert_eq!(code.as_deref(), Some("PGRST116"));
324            }
325            other => panic!("Expected PostgRest error, got {:?}", other),
326        }
327    }
328
329    #[test]
330    fn test_parse_error_response_valid_json_no_code() {
331        let body = r#"{"message":"Something went wrong"}"#;
332        let resp: SupabaseResponse<JsonValue> = parse_error_response(500, body);
333        assert!(resp.is_err());
334        match resp.error.as_ref().unwrap() {
335            SupabaseError::PostgRest { status, message, code } => {
336                assert_eq!(*status, 500);
337                assert_eq!(message, "Something went wrong");
338                assert!(code.is_none());
339            }
340            other => panic!("Expected PostgRest error, got {:?}", other),
341        }
342    }
343
344    #[test]
345    fn test_parse_error_response_no_message_field() {
346        let body = r#"{"error":"some error"}"#;
347        let resp: SupabaseResponse<JsonValue> = parse_error_response(400, body);
348        assert!(resp.is_err());
349        match resp.error.as_ref().unwrap() {
350            SupabaseError::PostgRest { message, .. } => {
351                assert_eq!(message, "Unknown error");
352            }
353            other => panic!("Expected PostgRest error, got {:?}", other),
354        }
355    }
356
357    #[test]
358    fn test_parse_error_response_invalid_json() {
359        let body = "this is not json";
360        let resp: SupabaseResponse<JsonValue> = parse_error_response(500, body);
361        assert!(resp.is_err());
362        match resp.error.as_ref().unwrap() {
363            SupabaseError::PostgRest { status, message, code } => {
364                assert_eq!(*status, 500);
365                assert_eq!(message, "this is not json");
366                assert!(code.is_none());
367            }
368            other => panic!("Expected PostgRest error, got {:?}", other),
369        }
370    }
371
372    // ---- execute_rest via wiremock ----
373
374    fn make_select_parts(table: &str) -> SqlParts {
375        SqlParts::new(SqlOperation::Select, "public", table)
376    }
377
378    #[tokio::test]
379    async fn test_execute_rest_select_json_array() {
380        let mock_server = MockServer::start().await;
381        Mock::given(method("GET"))
382            .and(path("/rest/v1/users"))
383            .respond_with(
384                ResponseTemplate::new(200)
385                    .set_body_json(json!([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}])),
386            )
387            .mount(&mock_server)
388            .await;
389
390        let http = reqwest::Client::new();
391        let url = format!("{}/rest/v1/users", mock_server.uri());
392        let headers = HeaderMap::new();
393        let parts = make_select_parts("users");
394
395        let resp: SupabaseResponse<JsonValue> =
396            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "test-key", "public", &parts).await;
397
398        assert!(resp.is_ok());
399        assert_eq!(resp.data.len(), 2);
400        assert_eq!(resp.data[0]["id"], 1);
401        assert_eq!(resp.data[0]["name"], "Alice");
402        assert_eq!(resp.data[1]["id"], 2);
403        assert_eq!(resp.data[1]["name"], "Bob");
404    }
405
406    #[tokio::test]
407    async fn test_execute_rest_select_single_object() {
408        let mock_server = MockServer::start().await;
409        Mock::given(method("GET"))
410            .and(path("/rest/v1/users"))
411            .respond_with(
412                ResponseTemplate::new(200)
413                    .set_body_json(json!({"id": 1, "name": "Alice"})),
414            )
415            .mount(&mock_server)
416            .await;
417
418        let http = reqwest::Client::new();
419        let url = format!("{}/rest/v1/users", mock_server.uri());
420        let headers = HeaderMap::new();
421        let mut parts = make_select_parts("users");
422        parts.single = true;
423
424        let resp: SupabaseResponse<JsonValue> =
425            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "test-key", "public", &parts).await;
426
427        assert!(resp.is_ok());
428        assert_eq!(resp.data.len(), 1);
429        assert_eq!(resp.data[0]["id"], 1);
430        assert_eq!(resp.data[0]["name"], "Alice");
431    }
432
433    #[tokio::test]
434    async fn test_execute_rest_head_with_count() {
435        let mock_server = MockServer::start().await;
436        Mock::given(method("HEAD"))
437            .and(path("/rest/v1/users"))
438            .respond_with(
439                ResponseTemplate::new(200)
440                    .insert_header("content-range", "0-9/42"),
441            )
442            .mount(&mock_server)
443            .await;
444
445        let http = reqwest::Client::new();
446        let url = format!("{}/rest/v1/users", mock_server.uri());
447        let headers = HeaderMap::new();
448        let mut parts = make_select_parts("users");
449        parts.head = true;
450
451        let resp: SupabaseResponse<JsonValue> =
452            execute_rest(&http, reqwest::Method::HEAD, &url, headers, None, "test-key", "public", &parts).await;
453
454        assert!(resp.is_ok());
455        assert!(resp.data.is_empty());
456        assert_eq!(resp.count, Some(42));
457    }
458
459    #[tokio::test]
460    async fn test_execute_rest_head_error_status() {
461        let mock_server = MockServer::start().await;
462        Mock::given(method("HEAD"))
463            .and(path("/rest/v1/users"))
464            .respond_with(ResponseTemplate::new(401))
465            .mount(&mock_server)
466            .await;
467
468        let http = reqwest::Client::new();
469        let url = format!("{}/rest/v1/users", mock_server.uri());
470        let headers = HeaderMap::new();
471        let mut parts = make_select_parts("users");
472        parts.head = true;
473
474        let resp: SupabaseResponse<JsonValue> =
475            execute_rest(&http, reqwest::Method::HEAD, &url, headers, None, "test-key", "public", &parts).await;
476
477        assert!(resp.is_err());
478        match resp.error.as_ref().unwrap() {
479            SupabaseError::PostgRest { status, .. } => {
480                assert_eq!(*status, 401);
481            }
482            other => panic!("Expected PostgRest error, got {:?}", other),
483        }
484    }
485
486    #[tokio::test]
487    async fn test_execute_rest_4xx_error_json_body() {
488        let mock_server = MockServer::start().await;
489        Mock::given(method("GET"))
490            .and(path("/rest/v1/users"))
491            .respond_with(
492                ResponseTemplate::new(400)
493                    .set_body_json(json!({"message": "Bad request", "code": "42703"})),
494            )
495            .mount(&mock_server)
496            .await;
497
498        let http = reqwest::Client::new();
499        let url = format!("{}/rest/v1/users", mock_server.uri());
500        let headers = HeaderMap::new();
501        let parts = make_select_parts("users");
502
503        let resp: SupabaseResponse<JsonValue> =
504            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "test-key", "public", &parts).await;
505
506        assert!(resp.is_err());
507        match resp.error.as_ref().unwrap() {
508            SupabaseError::PostgRest { status, message, code } => {
509                assert_eq!(*status, 400);
510                assert_eq!(message, "Bad request");
511                assert_eq!(code.as_deref(), Some("42703"));
512            }
513            other => panic!("Expected PostgRest error, got {:?}", other),
514        }
515    }
516
517    #[tokio::test]
518    async fn test_execute_rest_204_no_content() {
519        let mock_server = MockServer::start().await;
520        Mock::given(method("DELETE"))
521            .and(path("/rest/v1/users"))
522            .respond_with(ResponseTemplate::new(204))
523            .mount(&mock_server)
524            .await;
525
526        let http = reqwest::Client::new();
527        let url = format!("{}/rest/v1/users", mock_server.uri());
528        let headers = HeaderMap::new();
529        let mut parts = SqlParts::new(SqlOperation::Delete, "public", "users");
530        // No returning - expect 204
531        parts.returning = None;
532
533        let resp: SupabaseResponse<JsonValue> =
534            execute_rest(&http, reqwest::Method::DELETE, &url, headers, None, "test-key", "public", &parts).await;
535
536        assert!(resp.is_ok());
537        assert!(resp.data.is_empty());
538    }
539
540    #[tokio::test]
541    async fn test_execute_rest_empty_body_response() {
542        let mock_server = MockServer::start().await;
543        Mock::given(method("POST"))
544            .and(path("/rest/v1/users"))
545            .respond_with(ResponseTemplate::new(200).set_body_string(""))
546            .mount(&mock_server)
547            .await;
548
549        let http = reqwest::Client::new();
550        let url = format!("{}/rest/v1/users", mock_server.uri());
551        let headers = HeaderMap::new();
552        let parts = SqlParts::new(SqlOperation::Insert, "public", "users");
553
554        let resp: SupabaseResponse<JsonValue> =
555            execute_rest(&http, reqwest::Method::POST, &url, headers, None, "test-key", "public", &parts).await;
556
557        assert!(resp.is_ok());
558        assert!(resp.data.is_empty());
559    }
560
561    #[tokio::test]
562    async fn test_execute_rest_insert_returns_created_status() {
563        let mock_server = MockServer::start().await;
564        Mock::given(method("POST"))
565            .and(path("/rest/v1/users"))
566            .respond_with(
567                ResponseTemplate::new(201)
568                    .set_body_json(json!([{"id": 1, "name": "Alice"}])),
569            )
570            .mount(&mock_server)
571            .await;
572
573        let http = reqwest::Client::new();
574        let url = format!("{}/rest/v1/users", mock_server.uri());
575        let headers = HeaderMap::new();
576        let parts = SqlParts::new(SqlOperation::Insert, "public", "users");
577
578        let resp: SupabaseResponse<JsonValue> =
579            execute_rest(&http, reqwest::Method::POST, &url, headers, None, "test-key", "public", &parts).await;
580
581        assert!(resp.is_ok());
582        assert_eq!(resp.data.len(), 1);
583        assert_eq!(resp.status, supabase_client_core::StatusCode::Created);
584    }
585
586    #[tokio::test]
587    async fn test_execute_rest_with_count_header() {
588        let mock_server = MockServer::start().await;
589        Mock::given(method("GET"))
590            .and(path("/rest/v1/users"))
591            .respond_with(
592                ResponseTemplate::new(200)
593                    .set_body_json(json!([{"id": 1}]))
594                    .insert_header("content-range", "0-0/25"),
595            )
596            .mount(&mock_server)
597            .await;
598
599        let http = reqwest::Client::new();
600        let url = format!("{}/rest/v1/users", mock_server.uri());
601        let headers = HeaderMap::new();
602        let parts = make_select_parts("users");
603
604        let resp: SupabaseResponse<JsonValue> =
605            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "test-key", "public", &parts).await;
606
607        assert!(resp.is_ok());
608        assert_eq!(resp.count, Some(25));
609    }
610
611    #[tokio::test]
612    async fn test_execute_rest_sets_auth_headers() {
613        let mock_server = MockServer::start().await;
614        Mock::given(method("GET"))
615            .and(path("/rest/v1/users"))
616            .and(wiremock::matchers::header("apikey", "my-secret-key"))
617            .and(wiremock::matchers::header("Authorization", "Bearer my-secret-key"))
618            .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
619            .mount(&mock_server)
620            .await;
621
622        let http = reqwest::Client::new();
623        let url = format!("{}/rest/v1/users", mock_server.uri());
624        let headers = HeaderMap::new();
625        let parts = make_select_parts("users");
626
627        let resp: SupabaseResponse<JsonValue> =
628            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "my-secret-key", "public", &parts).await;
629
630        assert!(resp.is_ok());
631    }
632
633    #[tokio::test]
634    async fn test_execute_rest_non_public_schema_sets_profile() {
635        let mock_server = MockServer::start().await;
636        Mock::given(method("GET"))
637            .and(path("/rest/v1/users"))
638            .and(wiremock::matchers::header("Accept-Profile", "custom_schema"))
639            .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
640            .mount(&mock_server)
641            .await;
642
643        let http = reqwest::Client::new();
644        let url = format!("{}/rest/v1/users", mock_server.uri());
645        let headers = HeaderMap::new();
646        let parts = make_select_parts("users");
647
648        let resp: SupabaseResponse<JsonValue> =
649            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "key", "custom_schema", &parts).await;
650
651        assert!(resp.is_ok());
652    }
653
654    #[tokio::test]
655    async fn test_execute_rest_with_body() {
656        let mock_server = MockServer::start().await;
657        Mock::given(method("POST"))
658            .and(path("/rest/v1/users"))
659            .and(wiremock::matchers::body_json(json!({"name": "Alice"})))
660            .respond_with(
661                ResponseTemplate::new(201)
662                    .set_body_json(json!([{"id": 1, "name": "Alice"}])),
663            )
664            .mount(&mock_server)
665            .await;
666
667        let http = reqwest::Client::new();
668        let url = format!("{}/rest/v1/users", mock_server.uri());
669        let headers = HeaderMap::new();
670        let parts = SqlParts::new(SqlOperation::Insert, "public", "users");
671
672        let resp: SupabaseResponse<JsonValue> =
673            execute_rest(
674                &http, reqwest::Method::POST, &url, headers,
675                Some(json!({"name": "Alice"})), "key", "public", &parts,
676            ).await;
677
678        assert!(resp.is_ok());
679        assert_eq!(resp.data.len(), 1);
680    }
681
682    #[tokio::test]
683    async fn test_execute_rest_maybe_single_with_one_row() {
684        let mock_server = MockServer::start().await;
685        Mock::given(method("GET"))
686            .and(path("/rest/v1/users"))
687            .respond_with(
688                ResponseTemplate::new(200)
689                    .set_body_json(json!([{"id": 1}])),
690            )
691            .mount(&mock_server)
692            .await;
693
694        let http = reqwest::Client::new();
695        let url = format!("{}/rest/v1/users", mock_server.uri());
696        let headers = HeaderMap::new();
697        let mut parts = make_select_parts("users");
698        parts.maybe_single = true;
699
700        let resp: SupabaseResponse<JsonValue> =
701            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "key", "public", &parts).await;
702
703        assert!(resp.is_ok());
704        assert_eq!(resp.data.len(), 1);
705    }
706
707    #[tokio::test]
708    async fn test_execute_rest_maybe_single_with_multiple_rows_errors() {
709        let mock_server = MockServer::start().await;
710        Mock::given(method("GET"))
711            .and(path("/rest/v1/users"))
712            .respond_with(
713                ResponseTemplate::new(200)
714                    .set_body_json(json!([{"id": 1}, {"id": 2}, {"id": 3}])),
715            )
716            .mount(&mock_server)
717            .await;
718
719        let http = reqwest::Client::new();
720        let url = format!("{}/rest/v1/users", mock_server.uri());
721        let headers = HeaderMap::new();
722        let mut parts = make_select_parts("users");
723        parts.maybe_single = true;
724
725        let resp: SupabaseResponse<JsonValue> =
726            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "key", "public", &parts).await;
727
728        assert!(resp.is_err());
729        assert!(matches!(resp.error.as_ref().unwrap(), SupabaseError::MultipleRows(3)));
730    }
731
732    #[tokio::test]
733    async fn test_execute_rest_scalar_response() {
734        // When PostgREST returns a bare scalar (e.g., from an RPC function),
735        // the parser falls through to the single-object parse path since
736        // serde_json::Value can parse any valid JSON. The result is a Vec
737        // containing the scalar value directly.
738        let mock_server = MockServer::start().await;
739        Mock::given(method("GET"))
740            .and(path("/rest/v1/my_func"))
741            .respond_with(
742                ResponseTemplate::new(200).set_body_string("42"),
743            )
744            .mount(&mock_server)
745            .await;
746
747        let http = reqwest::Client::new();
748        let url = format!("{}/rest/v1/my_func", mock_server.uri());
749        let headers = HeaderMap::new();
750        let parts = make_select_parts("my_func");
751
752        let resp: SupabaseResponse<JsonValue> =
753            execute_rest(&http, reqwest::Method::GET, &url, headers, None, "key", "public", &parts).await;
754
755        assert!(resp.is_ok());
756        assert_eq!(resp.data.len(), 1);
757        // serde_json::Value parses "42" as Number(42) in the single-object fallback
758        assert_eq!(resp.data[0], serde_json::json!(42));
759    }
760}