diff --git a/cloudflare/.gitignore b/cloudflare/.gitignore new file mode 100644 index 0000000..58ce30f --- /dev/null +++ b/cloudflare/.gitignore @@ -0,0 +1 @@ +spec.yaml diff --git a/cloudflare/src/endpoints/d1/create_database.rs b/cloudflare/src/endpoints/d1/create_database.rs new file mode 100644 index 0000000..00f6fb7 --- /dev/null +++ b/cloudflare/src/endpoints/d1/create_database.rs @@ -0,0 +1,100 @@ +use super::data_structures::{D1Database, D1PrimaryLocationHint}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Create a new D1 database +/// +/// Creates a new D1 database with the specified name. +/// Database names must be unique within the account. +/// +/// +#[derive(Debug)] +pub struct CreateDatabase<'a> { + pub account_identifier: &'a str, + pub params: CreateDatabaseParams, +} + +impl<'a> CreateDatabase<'a> { + pub fn new(account_identifier: &'a str, params: CreateDatabaseParams) -> Self { + Self { + account_identifier, + params, + } + } +} + +impl EndpointSpec for CreateDatabase<'_> { + type JsonResponse = D1Database; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!("accounts/{}/d1/database", self.account_identifier) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for creating a D1 database +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct CreateDatabaseParams { + /// The name of the database to create + pub name: String, + /// Specify the region to create the D1 primary (optional) + pub primary_location_hint: Option, +} + +impl CreateDatabaseParams { + pub fn new(name: String) -> Self { + Self { + name, + primary_location_hint: None, + } + } + + pub fn with_location_hint(name: String, location_hint: D1PrimaryLocationHint) -> Self { + Self { + name, + primary_location_hint: Some(location_hint), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_database_params() { + let params = CreateDatabaseParams::new("test-db".to_string()); + assert_eq!(params.name, "test-db"); + assert_eq!(params.primary_location_hint, None); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"test-db"}"#; + assert_eq!(json, expected); + } + + #[test] + fn test_create_database_params_with_location() { + let params = CreateDatabaseParams::with_location_hint( + "test-db".to_string(), + D1PrimaryLocationHint::Weur + ); + assert_eq!(params.name, "test-db"); + assert_eq!(params.primary_location_hint, Some(D1PrimaryLocationHint::Weur)); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"test-db","primary_location_hint":"weur"}"#; + assert_eq!(json, expected); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/data_structures.rs b/cloudflare/src/endpoints/d1/data_structures.rs new file mode 100644 index 0000000..f807c1b --- /dev/null +++ b/cloudflare/src/endpoints/d1/data_structures.rs @@ -0,0 +1,309 @@ +use crate::framework::response::ApiResult; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// D1 Primary Location Hint +/// +/// Specify the region to create the D1 primary, if available. +/// If omitted, D1 will be created as close as possible to the current user. +/// +/// +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum D1PrimaryLocationHint { + /// Western North America + Wnam, + /// Eastern North America + Enam, + /// Western Europe + Weur, + /// Eastern Europe + Eeur, + /// Asia-Pacific + Apac, + /// Oceania + Oc, +} + +/// D1 Read Replication Mode +/// +/// Configuration for D1 read replication. +/// +/// +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum D1ReadReplicationMode { + /// Create replicas automatically and place them around the world + Auto, + /// Disable database replicas (takes a few hours to delete all replicas) + Disabled, +} + +/// D1 Read Replication Configuration +/// +/// +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct D1ReadReplicationConfig { + /// The read replication mode for the database + pub mode: D1ReadReplicationMode, +} + +/// D1 Read Replication Details +/// +/// Configuration details for D1 read replication. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct D1ReadReplicationDetails { + /// The read replication mode for the database + pub mode: String, +} + +/// D1 Query Timings +/// +/// Various durations for the query execution. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct D1QueryTimings { + /// SQL execution duration in milliseconds (optional) + pub sql_duration_ms: Option, +} + +/// D1 Query Metadata +/// +/// Metadata about query execution including performance and change information. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct D1QueryMeta { + /// Whether the database has been altered (optional) + pub changed_db: Option, + /// Number of rows changed by the query (optional) + pub changes: Option, + /// Query execution duration in milliseconds (optional) + pub duration: Option, + /// Last inserted row ID (optional) + pub last_row_id: Option, + /// Number of rows read (optional) + pub rows_read: Option, + /// Number of rows written (optional) + pub rows_written: Option, + /// Whether query was served by primary instance (optional) + pub served_by_primary: Option, + /// Region that served the query (optional) + pub served_by_region: Option, + /// Database size after the query (optional) + pub size_after: Option, + /// Various query durations (optional) + pub timings: Option, +} + +/// D1 Database +/// +/// Represents a D1 SQLite database instance in Cloudflare's serverless SQL database service. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct D1Database { + /// Database UUID identifier + pub uuid: String, + /// Human-readable database name + pub name: String, + /// Database version (optional) + pub version: Option, + /// Number of tables in the database (optional) + pub num_tables: Option, + /// Database file size in bytes (optional) + pub file_size: Option, + /// Region where the database is running (optional) + pub running_in_region: Option, + /// Database creation timestamp + pub created_at: String, + /// Read replication configuration (optional) + pub read_replication: Option, +} + +impl ApiResult for D1Database {} +impl ApiResult for Vec {} + +/// D1 Query Result +/// +/// Response from executing SQL queries against a D1 database. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct D1QueryResult { + /// Query result rows as JSON objects + pub results: Vec>, + /// Query execution metadata + pub meta: D1QueryMeta, + /// Whether the query was successful + pub success: bool, +} + +impl ApiResult for D1QueryResult {} +impl ApiResult for Vec {} + +/// D1 Raw Query Results +/// +/// Raw query results with columns and rows as arrays (performance-optimized). +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct D1RawQueryResults { + /// Column names + pub columns: Vec, + /// Rows as arrays of values + pub rows: Vec>, +} + +/// D1 Raw Query Result +/// +/// Response from executing raw SQL queries (performance-optimized format). +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] +pub struct D1RawQueryResult { + /// Raw query results with columns and rows + pub results: D1RawQueryResults, + /// Query execution metadata + pub meta: D1QueryMeta, + /// Whether the query was successful + pub success: bool, +} + +impl ApiResult for D1RawQueryResult {} + +/// D1 Export Result +/// +/// Result of a D1 database export operation. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct D1ExportResult { + /// Export operation ID for polling status + pub id: Option, + /// URL to download the exported SQL file (when ready) + pub url: Option, + /// Status of the export operation + pub status: Option, + /// Export expiry time + pub expires_at: Option, +} + +impl ApiResult for D1ExportResult {} + +/// D1 Import Result +/// +/// Result of a D1 database import operation. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct D1ImportResult { + /// Import operation ID for polling status + pub id: Option, + /// Upload URL for the SQL file + pub upload_url: Option, + /// Status of the import operation + pub status: Option, + /// Import completion time + pub completed_at: Option, +} + +impl ApiResult for D1ImportResult {} + +// Implement ApiResult for serde_json::Value for compatibility +impl ApiResult for serde_json::Value {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_d1_database_deserialization() { + let json = r#" + { + "uuid": "00000000-0000-0000-0000-000000000000", + "name": "test-db", + "version": "1.0", + "num_tables": 5, + "file_size": 1024, + "running_in_region": "weur", + "created_at": "2024-01-01T00:00:00.000Z", + "read_replication": { + "mode": "auto" + } + } + "#; + + let database: D1Database = serde_json::from_str(json).unwrap(); + assert_eq!(database.uuid, "00000000-0000-0000-0000-000000000000"); + assert_eq!(database.name, "test-db"); + assert_eq!(database.version, Some("1.0".to_string())); + assert_eq!(database.num_tables, Some(5)); + assert_eq!(database.file_size, Some(1024)); + assert_eq!(database.running_in_region, Some("weur".to_string())); + assert!(database.read_replication.is_some()); + assert_eq!(database.read_replication.unwrap().mode, "auto"); + } + + #[test] + fn test_d1_query_result_deserialization() { + let json = r#" + { + "results": [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} + ], + "meta": { + "served_by_region": "WEUR", + "duration": 15.5, + "changes": 0, + "last_row_id": null, + "changed_db": false, + "size_after": 2048, + "rows_read": 2, + "rows_written": 0, + "served_by_primary": true + }, + "success": true + } + "#; + + let result: D1QueryResult = serde_json::from_str(json).unwrap(); + assert_eq!(result.results.len(), 2); + assert_eq!(result.success, true); + assert_eq!(result.meta.served_by_region, Some("WEUR".to_string())); + assert_eq!(result.meta.duration, Some(15.5)); + assert_eq!(result.meta.rows_read, Some(2.0)); + } + + #[test] + fn test_d1_raw_query_result_deserialization() { + let json = r#" + { + "results": { + "columns": ["id", "name"], + "rows": [[1, "Alice"], [2, "Bob"]] + }, + "meta": { + "served_by_region": "EEUR", + "duration": 12.3, + "changes": 0, + "rows_read": 2, + "served_by_primary": true + }, + "success": true + } + "#; + + let result: D1RawQueryResult = serde_json::from_str(json).unwrap(); + assert_eq!(result.results.columns, vec!["id", "name"]); + assert_eq!(result.results.rows.len(), 2); + assert_eq!(result.success, true); + assert_eq!(result.meta.served_by_region, Some("EEUR".to_string())); + assert_eq!(result.meta.duration, Some(12.3)); + assert_eq!(result.meta.served_by_primary, Some(true)); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/delete_database.rs b/cloudflare/src/endpoints/d1/delete_database.rs new file mode 100644 index 0000000..f276af2 --- /dev/null +++ b/cloudflare/src/endpoints/d1/delete_database.rs @@ -0,0 +1,39 @@ +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Delete a D1 database +/// +/// Permanently deletes a D1 database and all its data. +/// This operation cannot be undone. +/// +/// +#[derive(Debug)] +pub struct DeleteDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, +} + +impl<'a> DeleteDatabase<'a> { + pub fn new(account_identifier: &'a str, database_identifier: &'a str) -> Self { + Self { + account_identifier, + database_identifier, + } + } +} + +impl EndpointSpec for DeleteDatabase<'_> { + type JsonResponse = serde_json::Value; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::DELETE + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}", + self.account_identifier, self.database_identifier + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/export_database.rs b/cloudflare/src/endpoints/d1/export_database.rs new file mode 100644 index 0000000..bd66e2b --- /dev/null +++ b/cloudflare/src/endpoints/d1/export_database.rs @@ -0,0 +1,82 @@ +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Export a D1 database as SQL +/// +/// Returns a URL where the SQL contents of your D1 can be downloaded. +/// Note: this process may take some time for larger DBs, during which +/// your D1 will be unavailable to serve queries. To avoid blocking +/// your DB unnecessarily, an in-progress export must be continually +/// polled or will automatically cancel. +/// +/// +#[derive(Debug)] +pub struct ExportDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: ExportDatabaseParams, +} + +impl<'a> ExportDatabase<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: ExportDatabaseParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for ExportDatabase<'_> { + type JsonResponse = D1ExportResult; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}/export", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +use super::data_structures::D1ExportResult; + +/// Parameters for exporting a D1 database +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct ExportDatabaseParams { + /// Output format for the export (optional) + pub format: Option, +} + +impl ExportDatabaseParams { + pub fn new() -> Self { + Self { format: None } + } + + pub fn with_format(format: String) -> Self { + Self { + format: Some(format), + } + } +} + +impl Default for ExportDatabaseParams { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/get_database.rs b/cloudflare/src/endpoints/d1/get_database.rs new file mode 100644 index 0000000..a6a9e2f --- /dev/null +++ b/cloudflare/src/endpoints/d1/get_database.rs @@ -0,0 +1,40 @@ +use super::data_structures::D1Database; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get details of a specific D1 database +/// +/// Retrieves detailed information about a D1 database by its UUID. +/// +/// +#[derive(Debug)] +pub struct GetDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, +} + +impl<'a> GetDatabase<'a> { + pub fn new(account_identifier: &'a str, database_identifier: &'a str) -> Self { + Self { + account_identifier, + database_identifier, + } + } +} + +impl EndpointSpec for GetDatabase<'_> { + type JsonResponse = D1Database; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}", + self.account_identifier, self.database_identifier + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/import_database.rs b/cloudflare/src/endpoints/d1/import_database.rs new file mode 100644 index 0000000..5de843f --- /dev/null +++ b/cloudflare/src/endpoints/d1/import_database.rs @@ -0,0 +1,93 @@ +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Import SQL into a D1 database +/// +/// Generates a temporary URL for uploading an SQL file to, then instructing +/// the D1 to import it and polling it for status updates. Imports block +/// the D1 for their duration. +/// +/// +#[derive(Debug)] +pub struct ImportDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: ImportDatabaseParams, +} + +impl<'a> ImportDatabase<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: ImportDatabaseParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for ImportDatabase<'_> { + type JsonResponse = D1ImportResult; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}/import", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +use super::data_structures::D1ImportResult; + +/// Parameters for importing to a D1 database +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct ImportDatabaseParams { + /// SQL content to import (optional - if not provided, use upload_url) + pub sql: Option, + /// File name for the import operation (optional) + pub file_name: Option, +} + +impl ImportDatabaseParams { + pub fn new() -> Self { + Self { + sql: None, + file_name: None, + } + } + + pub fn with_sql(sql: String) -> Self { + Self { + sql: Some(sql), + file_name: None, + } + } + + pub fn with_file_name(file_name: String) -> Self { + Self { + sql: None, + file_name: Some(file_name), + } + } +} + +impl Default for ImportDatabaseParams { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/list_databases.rs b/cloudflare/src/endpoints/d1/list_databases.rs new file mode 100644 index 0000000..94682c9 --- /dev/null +++ b/cloudflare/src/endpoints/d1/list_databases.rs @@ -0,0 +1,35 @@ +use super::data_structures::D1Database; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List all D1 databases in an account +/// +/// Returns a list of all D1 databases owned by the account. +/// +/// +#[derive(Debug)] +pub struct ListDatabases<'a> { + pub account_identifier: &'a str, +} + +impl<'a> ListDatabases<'a> { + pub fn new(account_identifier: &'a str) -> Self { + Self { + account_identifier, + } + } +} + +impl EndpointSpec for ListDatabases<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!("accounts/{}/d1/database", self.account_identifier) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/mod.rs b/cloudflare/src/endpoints/d1/mod.rs new file mode 100644 index 0000000..e6cdcaf --- /dev/null +++ b/cloudflare/src/endpoints/d1/mod.rs @@ -0,0 +1,39 @@ +/*! +D1 database endpoints for Cloudflare's serverless SQL database service. + +This module provides comprehensive D1 database management capabilities including: +- Database CRUD operations (create, read, update, delete) +- SQL query execution (parameterized and raw queries) +- Data import/export operations +- Read replication configuration + +All endpoints are fully compliant with the official Cloudflare API specification. +*/ + +pub mod create_database; +pub mod data_structures; +pub mod delete_database; +pub mod export_database; +pub mod get_database; +pub mod import_database; +pub mod list_databases; +pub mod query_database; +pub mod raw_query; +pub mod update_database; +pub mod update_partial_database; + +pub use create_database::{CreateDatabase, CreateDatabaseParams}; +pub use data_structures::{ + D1Database, D1ExportResult, D1ImportResult, D1PrimaryLocationHint, D1QueryMeta, + D1QueryResult, D1QueryTimings, D1RawQueryResult, D1RawQueryResults, D1ReadReplicationConfig, + D1ReadReplicationDetails, D1ReadReplicationMode, +}; +pub use delete_database::DeleteDatabase; +pub use export_database::{ExportDatabase, ExportDatabaseParams}; +pub use get_database::GetDatabase; +pub use import_database::{ImportDatabase, ImportDatabaseParams}; +pub use list_databases::ListDatabases; +pub use query_database::{QueryDatabase, QueryDatabaseParams}; +pub use raw_query::{RawQuery, RawQueryParams}; +pub use update_database::{UpdateDatabase, UpdateDatabaseParams}; +pub use update_partial_database::{UpdatePartialDatabase, UpdatePartialDatabaseParams}; \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/query_database.rs b/cloudflare/src/endpoints/d1/query_database.rs new file mode 100644 index 0000000..2cc92e4 --- /dev/null +++ b/cloudflare/src/endpoints/d1/query_database.rs @@ -0,0 +1,95 @@ +use super::data_structures::D1QueryResult; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Execute a parameterized SQL query against a D1 database +/// +/// Executes a SQL query with optional parameters against the specified D1 database. +/// This is the recommended way to execute queries as it supports parameterization +/// which helps prevent SQL injection attacks. +/// +/// +#[derive(Debug)] +pub struct QueryDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: QueryDatabaseParams, +} + +impl<'a> QueryDatabase<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: QueryDatabaseParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for QueryDatabase<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}/query", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for executing a parameterized SQL query +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct QueryDatabaseParams { + /// The SQL statement to execute + pub sql: String, + /// Parameters to bind to the SQL statement + pub params: Vec, +} + +impl QueryDatabaseParams { + pub fn new(sql: String) -> Self { + Self { + sql, + params: vec![], + } + } + + pub fn with_params(sql: String, params: Vec) -> Self { + Self { sql, params } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_query_database_params() { + let params = QueryDatabaseParams::new("SELECT * FROM users".to_string()); + assert_eq!(params.sql, "SELECT * FROM users"); + assert!(params.params.is_empty()); + + let params_with_bindings = QueryDatabaseParams::with_params( + "SELECT * FROM users WHERE id = ?".to_string(), + vec![serde_json::Value::Number(serde_json::Number::from(1))] + ); + assert_eq!(params_with_bindings.sql, "SELECT * FROM users WHERE id = ?"); + assert_eq!(params_with_bindings.params.len(), 1); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/raw_query.rs b/cloudflare/src/endpoints/d1/raw_query.rs new file mode 100644 index 0000000..ca8b2f4 --- /dev/null +++ b/cloudflare/src/endpoints/d1/raw_query.rs @@ -0,0 +1,67 @@ +use super::data_structures::D1RawQueryResult; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Execute raw SQL against a D1 database +/// +/// Executes raw SQL statements against the specified D1 database. +/// This endpoint is useful for administrative operations and bulk operations +/// but should be used with caution as it doesn't support parameterization. +/// +/// +#[derive(Debug)] +pub struct RawQuery<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: RawQueryParams, +} + +impl<'a> RawQuery<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: RawQueryParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for RawQuery<'_> { + type JsonResponse = D1RawQueryResult; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}/raw", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for executing raw SQL +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct RawQueryParams { + /// The raw SQL to execute + pub sql: String, +} + +impl RawQueryParams { + pub fn new(sql: String) -> Self { + Self { sql } + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/update_database.rs b/cloudflare/src/endpoints/d1/update_database.rs new file mode 100644 index 0000000..32a7d44 --- /dev/null +++ b/cloudflare/src/endpoints/d1/update_database.rs @@ -0,0 +1,67 @@ +use super::data_structures::{D1Database, D1ReadReplicationMode, D1ReadReplicationConfig}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Update a D1 database +/// +/// Updates configuration for an existing D1 database. +/// +/// +#[derive(Debug)] +pub struct UpdateDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: UpdateDatabaseParams, +} + +impl<'a> UpdateDatabase<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: UpdateDatabaseParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for UpdateDatabase<'_> { + type JsonResponse = D1Database; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PUT + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for updating a D1 database +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct UpdateDatabaseParams { + /// Configuration for D1 read replication + pub read_replication: D1ReadReplicationConfig, +} + +impl UpdateDatabaseParams { + pub fn new(mode: D1ReadReplicationMode) -> Self { + Self { + read_replication: D1ReadReplicationConfig { mode }, + } + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/d1/update_partial_database.rs b/cloudflare/src/endpoints/d1/update_partial_database.rs new file mode 100644 index 0000000..35312a5 --- /dev/null +++ b/cloudflare/src/endpoints/d1/update_partial_database.rs @@ -0,0 +1,81 @@ +use super::data_structures::{D1Database, D1ReadReplicationConfig}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Partially update a D1 database +/// +/// Partially updates configuration for an existing D1 database. +/// Only provided fields will be updated. +/// +/// +#[derive(Debug)] +pub struct UpdatePartialDatabase<'a> { + pub account_identifier: &'a str, + pub database_identifier: &'a str, + pub params: UpdatePartialDatabaseParams, +} + +impl<'a> UpdatePartialDatabase<'a> { + pub fn new( + account_identifier: &'a str, + database_identifier: &'a str, + params: UpdatePartialDatabaseParams, + ) -> Self { + Self { + account_identifier, + database_identifier, + params, + } + } +} + +impl EndpointSpec for UpdatePartialDatabase<'_> { + type JsonResponse = D1Database; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PATCH + } + + fn path(&self) -> String { + format!( + "accounts/{}/d1/database/{}", + self.account_identifier, self.database_identifier + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for partially updating a D1 database +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct UpdatePartialDatabaseParams { + /// Configuration for D1 read replication (optional) + pub read_replication: Option, +} + +impl UpdatePartialDatabaseParams { + pub fn new() -> Self { + Self { + read_replication: None, + } + } + + pub fn with_read_replication(read_replication: D1ReadReplicationConfig) -> Self { + Self { + read_replication: Some(read_replication), + } + } +} + +impl Default for UpdatePartialDatabaseParams { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/mod.rs b/cloudflare/src/endpoints/mod.rs index f590cc9..afaa275 100644 --- a/cloudflare/src/endpoints/mod.rs +++ b/cloudflare/src/endpoints/mod.rs @@ -7,6 +7,7 @@ pub mod account; pub mod ai; pub mod argo_tunnel; pub mod cfd_tunnel; +pub mod d1; pub mod dns; pub mod load_balancing; pub mod r2;