Skip to main content

fraiseql_wire/json/validate/
mod.rs

1//! Result schema validation
2
3use crate::protocol::{BackendMessage, FieldDescription};
4use crate::util::oid::is_json_oid;
5use crate::{Result, WireError};
6
7/// Validate that `RowDescription` matches our requirements
8///
9/// # Errors
10///
11/// Returns [`WireError::Protocol`] if the message is not a `RowDescription`.
12/// Returns [`WireError::InvalidSchema`] if the description does not have exactly one column
13/// named `"data"` with a JSON or JSONB type OID.
14pub fn validate_row_description(msg: &BackendMessage) -> Result<()> {
15    let fields = match msg {
16        BackendMessage::RowDescription(fields) => fields,
17        _ => return Err(WireError::Protocol("expected RowDescription".into())),
18    };
19
20    // Must have exactly one column
21    let field = match fields.as_slice() {
22        [only] => only,
23        _ => {
24            return Err(WireError::InvalidSchema(format!(
25                "expected 1 column, got {}",
26                fields.len()
27            )));
28        }
29    };
30
31    // Column must be named "data"
32    if field.name != "data" {
33        return Err(WireError::InvalidSchema(format!(
34            "expected column named 'data', got '{}'",
35            field.name
36        )));
37    }
38
39    // Type must be json or jsonb
40    if !is_json_oid(field.type_oid) {
41        return Err(WireError::InvalidSchema(format!(
42            "expected json/jsonb type, got OID {}",
43            field.type_oid
44        )));
45    }
46
47    Ok(())
48}
49
50/// Extract field description from `RowDescription`
51///
52/// # Errors
53///
54/// Returns [`WireError::Protocol`] if the message is not a `RowDescription`.
55pub fn extract_field_description(msg: &BackendMessage) -> Result<FieldDescription> {
56    let fields = match msg {
57        BackendMessage::RowDescription(fields) => fields,
58        _ => return Err(WireError::Protocol("expected RowDescription".into())),
59    };
60
61    fields
62        .first()
63        .cloned()
64        .ok_or_else(|| WireError::Protocol("RowDescription has no fields".into()))
65}
66
67#[cfg(test)]
68mod tests;