Skip to main content

fraiseql_cli/config/toml_schema/rest/
mod.rs

1//! REST transport TOML configuration.
2
3use fraiseql_core::schema::{DeleteResponse, RestConfig};
4use serde::{Deserialize, Serialize};
5
6/// REST transport configuration parsed from the `[rest]` TOML section.
7///
8/// All fields have defaults matching `RestConfig::default()` in `fraiseql-core`.
9/// When `enabled` is `false` (the default), the REST transport is not mounted.
10#[derive(Debug, Clone, Deserialize, Serialize)]
11#[serde(default)]
12pub struct RestTomlConfig {
13    /// Whether the REST transport is enabled.
14    pub enabled:                 bool,
15    /// Base URL path for REST endpoints.
16    pub path:                    String,
17    /// Maximum rows per page (clamps `?limit=`).
18    pub max_page_size:           u64,
19    /// Default page size when no `?limit=` is specified.
20    pub default_page_size:       u64,
21    /// Batch size for NDJSON streaming responses.
22    pub ndjson_batch_size:       u64,
23    /// Maximum affected rows for bulk PATCH/DELETE.
24    pub max_bulk_affected:       u64,
25    /// Maximum byte length for `?filter=` JSON values.
26    pub max_filter_bytes:        u64,
27    /// How DELETE endpoints report success: `"no_content"` (default) or `"entity"`.
28    pub delete_response:         DeleteResponseToml,
29    /// Default result cache TTL in seconds (0 = no caching).
30    pub default_cache_ttl:       u64,
31    /// CDN `s-maxage` value in seconds (`None` = omit).
32    pub cdn_max_age:             Option<u64>,
33    /// Whether REST endpoints require authentication by default.
34    pub require_auth:            bool,
35    /// SSE heartbeat interval in seconds.
36    pub sse_heartbeat_seconds:   u64,
37    /// Maximum depth for resource embedding (`?select=posts(comments)`).
38    pub max_embedding_depth:     u32,
39    /// Allowlist of type names to expose as REST resources (empty = all).
40    pub include:                 Vec<String>,
41    /// Denylist of type names to exclude from REST resources.
42    pub exclude:                 Vec<String>,
43    /// Whether to enable `ETag` / `If-None-Match` conditional response support.
44    pub etag:                    bool,
45    /// TTL in seconds for idempotency key deduplication.
46    pub idempotency_ttl_seconds: u64,
47}
48
49/// DELETE response mode for TOML configuration.
50///
51/// Uses lowercase serde names for TOML ergonomics.
52#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
53#[serde(rename_all = "snake_case")]
54pub enum DeleteResponseToml {
55    /// Return `204 No Content`.
56    #[default]
57    NoContent,
58    /// Return `200` with the deleted entity in the body.
59    Entity,
60}
61
62impl Default for RestTomlConfig {
63    fn default() -> Self {
64        Self {
65            enabled:                 false,
66            path:                    "/rest/v1".to_string(),
67            max_page_size:           1_000,
68            default_page_size:       100,
69            ndjson_batch_size:       500,
70            max_bulk_affected:       10_000,
71            max_filter_bytes:        4_096,
72            delete_response:         DeleteResponseToml::NoContent,
73            default_cache_ttl:       0,
74            cdn_max_age:             None,
75            require_auth:            false,
76            sse_heartbeat_seconds:   30,
77            max_embedding_depth:     3,
78            include:                 Vec::new(),
79            exclude:                 Vec::new(),
80            etag:                    true,
81            idempotency_ttl_seconds: 300,
82        }
83    }
84}
85
86impl From<DeleteResponseToml> for DeleteResponse {
87    fn from(toml: DeleteResponseToml) -> Self {
88        match toml {
89            DeleteResponseToml::NoContent => Self::NoContent,
90            DeleteResponseToml::Entity => Self::Entity,
91        }
92    }
93}
94
95impl From<RestTomlConfig> for RestConfig {
96    fn from(toml: RestTomlConfig) -> Self {
97        Self {
98            enabled:                 toml.enabled,
99            path:                    toml.path,
100            max_page_size:           toml.max_page_size,
101            default_page_size:       toml.default_page_size,
102            ndjson_batch_size:       toml.ndjson_batch_size,
103            max_bulk_affected:       toml.max_bulk_affected,
104            max_filter_bytes:        toml.max_filter_bytes,
105            delete_response:         toml.delete_response.into(),
106            default_cache_ttl:       toml.default_cache_ttl,
107            cdn_max_age:             toml.cdn_max_age,
108            require_auth:            toml.require_auth,
109            sse_heartbeat_seconds:   toml.sse_heartbeat_seconds,
110            max_embedding_depth:     toml.max_embedding_depth,
111            include:                 toml.include,
112            exclude:                 toml.exclude,
113            etag:                    toml.etag,
114            idempotency_ttl_seconds: toml.idempotency_ttl_seconds,
115        }
116    }
117}
118
119#[cfg(test)]
120#[allow(clippy::unwrap_used)] // Reason: test code, panics are acceptable
121mod tests;