diff --git a/cloudflare/.gitignore b/cloudflare/.gitignore new file mode 100644 index 00000000..58ce30f1 --- /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 00000000..00f6fb70 --- /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 00000000..f807c1bf --- /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 00000000..f276af27 --- /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 00000000..bd66e2b3 --- /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 00000000..a6a9e2f7 --- /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 00000000..5de843fd --- /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 00000000..94682c97 --- /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 00000000..e6cdcaf8 --- /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 00000000..2cc92e4e --- /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 00000000..ca8b2f47 --- /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 00000000..32a7d444 --- /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 00000000..35312a52 --- /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 f590cc90..abb25850 100644 --- a/cloudflare/src/endpoints/mod.rs +++ b/cloudflare/src/endpoints/mod.rs @@ -7,8 +7,10 @@ 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 pages; pub mod r2; pub mod workers; pub mod workerskv; diff --git a/cloudflare/src/endpoints/pages/add_domain.rs b/cloudflare/src/endpoints/pages/add_domain.rs new file mode 100644 index 00000000..1e27fa6e --- /dev/null +++ b/cloudflare/src/endpoints/pages/add_domain.rs @@ -0,0 +1,81 @@ +use super::data_structures::PagesDomain; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Add a custom domain to a Pages project +/// +/// Associates a custom domain with a Pages project. +/// +/// +#[derive(Debug)] +pub struct AddDomain<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub params: AddDomainParams, +} + +impl<'a> AddDomain<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + params: AddDomainParams, + ) -> Self { + Self { + account_identifier, + project_name, + params, + } + } +} + +impl EndpointSpec for AddDomain<'_> { + type JsonResponse = PagesDomain; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/domains", + self.account_identifier, self.project_name + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for adding a domain to a Pages project +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct AddDomainParams { + /// The custom domain name to add + pub name: String, +} + +impl AddDomainParams { + /// Create parameters for adding a domain + pub fn new(name: String) -> Self { + Self { name } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_domain_params() { + let params = AddDomainParams::new("example.com".to_string()); + assert_eq!(params.name, "example.com"); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"example.com"}"#; + assert_eq!(json, expected); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/create_deployment.rs b/cloudflare/src/endpoints/pages/create_deployment.rs new file mode 100644 index 00000000..11115ada --- /dev/null +++ b/cloudflare/src/endpoints/pages/create_deployment.rs @@ -0,0 +1,185 @@ +use super::data_structures::{PagesBuildConfig, PagesDeployment}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; +use std::collections::HashMap; + +/// Create a new deployment for a Pages project +/// +/// Creates a new deployment from the latest commit in the production branch, +/// or from uploaded files for manual deployments. +/// +/// +#[derive(Debug)] +pub struct CreateDeployment<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub params: CreateDeploymentParams, +} + +impl<'a> CreateDeployment<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + params: CreateDeploymentParams, + ) -> Self { + Self { + account_identifier, + project_name, + params, + } + } +} + +impl EndpointSpec for CreateDeployment<'_> { + type JsonResponse = PagesDeployment; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments", + self.account_identifier, self.project_name + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for creating a deployment +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct CreateDeploymentParams { + /// Build configuration for this deployment (optional) + pub build_config: Option, + /// Environment variables for this deployment (optional) + pub env_vars: Option>, + /// Manifest of files to upload (for direct uploads, optional) + pub manifest: Option>, + /// Branch to deploy from (optional, defaults to production branch) + pub branch: Option, +} + +impl CreateDeploymentParams { + /// Create a deployment from the default production branch + pub fn new() -> Self { + Self { + build_config: None, + env_vars: None, + manifest: None, + branch: None, + } + } + + /// Create a deployment from a specific branch + pub fn from_branch(branch: String) -> Self { + Self { + build_config: None, + env_vars: None, + manifest: None, + branch: Some(branch), + } + } + + /// Create a deployment with direct file upload + pub fn with_manifest(manifest: HashMap) -> Self { + Self { + build_config: None, + env_vars: None, + manifest: Some(manifest), + branch: None, + } + } + + /// Add build configuration + pub fn with_build_config(mut self, build_config: PagesBuildConfig) -> Self { + self.build_config = Some(build_config); + self + } + + /// Add environment variables + pub fn with_env_vars(mut self, env_vars: HashMap) -> Self { + self.env_vars = Some(env_vars); + self + } + + /// Set the branch to deploy from + pub fn with_branch(mut self, branch: String) -> Self { + self.branch = Some(branch); + self + } +} + +impl Default for CreateDeploymentParams { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_deployment_params_default() { + let params = CreateDeploymentParams::new(); + assert_eq!(params.build_config, None); + assert_eq!(params.env_vars, None); + assert_eq!(params.manifest, None); + assert_eq!(params.branch, None); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = "{}"; + assert_eq!(json, expected); + } + + #[test] + fn test_create_deployment_params_from_branch() { + let params = CreateDeploymentParams::from_branch("development".to_string()); + assert_eq!(params.branch, Some("development".to_string())); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"branch":"development"}"#; + assert_eq!(json, expected); + } + + #[test] + fn test_create_deployment_params_with_manifest() { + let mut manifest = HashMap::new(); + manifest.insert("index.html".to_string(), "content-hash-123".to_string()); + manifest.insert("style.css".to_string(), "content-hash-456".to_string()); + + let params = CreateDeploymentParams::with_manifest(manifest.clone()); + assert_eq!(params.manifest, Some(manifest)); + } + + #[test] + fn test_create_deployment_params_with_env_vars() { + let mut env_vars = HashMap::new(); + env_vars.insert("API_KEY".to_string(), "secret123".to_string()); + env_vars.insert("NODE_ENV".to_string(), "production".to_string()); + + let params = CreateDeploymentParams::new().with_env_vars(env_vars.clone()); + assert_eq!(params.env_vars, Some(env_vars)); + } + + #[test] + fn test_create_deployment_params_chaining() { + let mut env_vars = HashMap::new(); + env_vars.insert("NODE_ENV".to_string(), "staging".to_string()); + + let params = CreateDeploymentParams::new() + .with_branch("staging".to_string()) + .with_env_vars(env_vars.clone()); + + assert_eq!(params.branch, Some("staging".to_string())); + assert_eq!(params.env_vars, Some(env_vars)); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/create_project.rs b/cloudflare/src/endpoints/pages/create_project.rs new file mode 100644 index 00000000..c6ed93be --- /dev/null +++ b/cloudflare/src/endpoints/pages/create_project.rs @@ -0,0 +1,198 @@ +use super::data_structures::{PagesBuildConfig, PagesDeploymentConfigs, PagesProject, PagesSource}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; +use std::collections::HashMap; + +/// Create a new Pages project +/// +/// Creates a new Pages project with the specified configuration. +/// Project names must be unique within the account. +/// +/// +#[derive(Debug)] +pub struct CreateProject<'a> { + pub account_identifier: &'a str, + pub params: CreateProjectParams, +} + +impl<'a> CreateProject<'a> { + pub fn new(account_identifier: &'a str, params: CreateProjectParams) -> Self { + Self { + account_identifier, + params, + } + } +} + +impl EndpointSpec for CreateProject<'_> { + type JsonResponse = PagesProject; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!("accounts/{}/pages/projects", self.account_identifier) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for creating a Pages project +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct CreateProjectParams { + /// The name of the project to create + pub name: String, + /// The subdomain for the project (optional, defaults to name if not provided) + pub subdomain: Option, + /// Production branch for the project (optional, defaults to "main") + pub production_branch: Option, + /// List of custom domains for the project (optional) + pub domains: Option>, + /// Source configuration for Git integration (optional) + pub source: Option, + /// Build configuration for the project (optional) + pub build_config: Option, + /// Deployment configurations for different environments (optional) + pub deployment_configs: Option, +} + +impl CreateProjectParams { + /// Create a new project with just a name + pub fn new(name: String) -> Self { + Self { + name, + subdomain: None, + production_branch: None, + domains: None, + source: None, + build_config: None, + deployment_configs: None, + } + } + + /// Create a new project with name and subdomain + pub fn with_subdomain(name: String, subdomain: String) -> Self { + Self { + name, + subdomain: Some(subdomain), + production_branch: None, + domains: None, + source: None, + build_config: None, + deployment_configs: None, + } + } + + /// Create a new project with Git source integration + pub fn with_git_source( + name: String, + repo_type: String, + repo_config: HashMap, + ) -> Self { + let source = PagesSource { + source_type: repo_type, + config: repo_config, + }; + + Self { + name, + subdomain: None, + production_branch: None, + domains: None, + source: Some(source), + build_config: None, + deployment_configs: None, + } + } + + /// Set production branch for the project + pub fn with_production_branch(mut self, production_branch: String) -> Self { + self.production_branch = Some(production_branch); + self + } + + /// Add custom domains to the project + pub fn with_domains(mut self, domains: Vec) -> Self { + self.domains = Some(domains); + self + } + + /// Set build configuration + pub fn with_build_config(mut self, build_config: PagesBuildConfig) -> Self { + self.build_config = Some(build_config); + self + } + + /// Set deployment configurations + pub fn with_deployment_configs(mut self, deployment_configs: PagesDeploymentConfigs) -> Self { + self.deployment_configs = Some(deployment_configs); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_project_params_basic() { + let params = CreateProjectParams::new("my-project".to_string()); + assert_eq!(params.name, "my-project"); + assert_eq!(params.subdomain, None); + assert_eq!(params.domains, None); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"my-project"}"#; + assert_eq!(json, expected); + } + + #[test] + fn test_create_project_params_with_subdomain() { + let params = CreateProjectParams::with_subdomain( + "my-project".to_string(), + "custom-subdomain".to_string(), + ); + assert_eq!(params.name, "my-project"); + assert_eq!(params.subdomain, Some("custom-subdomain".to_string())); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"my-project","subdomain":"custom-subdomain"}"#; + assert_eq!(json, expected); + } + + #[test] + fn test_create_project_params_with_domains() { + let params = CreateProjectParams::new("my-project".to_string()) + .with_domains(vec!["example.com".to_string(), "www.example.com".to_string()]); + + assert_eq!(params.domains, Some(vec!["example.com".to_string(), "www.example.com".to_string()])); + } + + #[test] + fn test_create_project_params_with_git_source() { + let mut repo_config = HashMap::new(); + repo_config.insert("owner".to_string(), serde_json::Value::String("user".to_string())); + repo_config.insert("repo_name".to_string(), serde_json::Value::String("my-repo".to_string())); + + let params = CreateProjectParams::with_git_source( + "my-project".to_string(), + "github".to_string(), + repo_config.clone(), + ); + + assert_eq!(params.name, "my-project"); + assert!(params.source.is_some()); + + let source = params.source.unwrap(); + assert_eq!(source.source_type, "github"); + assert_eq!(source.config, repo_config); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/data_structures.rs b/cloudflare/src/endpoints/pages/data_structures.rs new file mode 100644 index 00000000..83e8bdfe --- /dev/null +++ b/cloudflare/src/endpoints/pages/data_structures.rs @@ -0,0 +1,374 @@ +use crate::framework::response::ApiResult; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Pages Build Configuration +/// +/// Configuration settings for building a Pages project. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesBuildConfig { + /// Enable build caching for the project (optional) + pub build_caching: Option, + /// Command used to build project (optional) + pub build_command: Option, + /// Output directory of the build (optional) + pub destination_dir: Option, + /// Directory to run the command (optional) + pub root_dir: Option, + /// The classifying tag for analytics (optional) + pub web_analytics_tag: Option, + /// Environment variables for the build process (optional) + pub env_vars: Option>, +} + +impl Default for PagesBuildConfig { + fn default() -> Self { + Self { + build_caching: None, + build_command: None, + destination_dir: None, + root_dir: None, + web_analytics_tag: None, + env_vars: None, + } + } +} + +/// Pages Deployment Trigger Metadata +/// +/// Additional information about what caused the deployment. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentTriggerMetadata { + /// Where the trigger happened (optional) + pub branch: Option, + /// Hash of the deployment trigger commit (optional) + pub commit_hash: Option, + /// Message of the deployment trigger commit (optional) + pub commit_message: Option, +} + +/// Pages Deployment Trigger Type +/// +/// What caused the deployment. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum PagesDeploymentTriggerType { + /// Triggered by a git push + Push, + /// Triggered manually/ad-hoc + AdHoc, +} + +/// Pages Deployment Trigger +/// +/// Information about what caused the deployment. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentTrigger { + /// What caused the deployment + #[serde(rename = "type")] + pub trigger_type: PagesDeploymentTriggerType, + /// Additional info about the trigger (optional) + pub metadata: Option, +} + +/// Pages Deployment Stage +/// +/// Information about a deployment stage (build, deploy, etc.). +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentStage { + /// Stage name + pub name: String, + /// Stage started timestamp (optional) + pub started_on: Option, + /// Stage ended timestamp (optional) + pub ended_on: Option, + /// Stage status + pub status: String, +} + +/// Pages Deployment +/// +/// Represents a deployment of a Pages project. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeployment { + /// Deployment identifier + pub id: String, + /// Short identifier for the deployment + pub short_id: String, + /// Project identifier + pub project_id: String, + /// Project name + pub project_name: String, + /// Environment the deployment is for (production or preview) + pub environment: String, + /// Deployment URL + pub url: String, + /// When the deployment was created + pub created_on: String, + /// When the deployment was last modified (optional) + pub modified_on: Option, + /// List of alias URLs pointing to this deployment (optional) + pub aliases: Option>, + /// Build configuration used for this deployment (optional) + pub build_config: Option, + /// Source information (optional) + pub source: Option>, + /// Deployment trigger information (optional) + pub deployment_trigger: Option, + /// Deployment stages (optional) + pub stages: Option>, + /// Environment variables for the deployment (optional) + pub env_vars: Option>, + /// Latest deployment status + pub latest_stage: Option, +} + +impl ApiResult for PagesDeployment {} +impl ApiResult for Vec {} + +/// Pages Deployment Configuration +/// +/// Configuration for deployments in different environments. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentConfig { + /// Build configuration + pub build_config: Option, + /// Environment variables + pub env_vars: Option>, + /// KV namespace bindings (optional) + pub kv_namespaces: Option>, + /// Durable Object bindings (optional) + pub durable_objects: Option>, + /// D1 database bindings (optional) + pub d1_databases: Option>, + /// R2 bucket bindings (optional) + pub r2_buckets: Option>, + /// Service bindings (optional) + pub services: Option>, + /// Compatibility date (optional) + pub compatibility_date: Option, + /// Compatibility flags (optional) + pub compatibility_flags: Option>, +} + +/// Pages Deployment Configurations +/// +/// Deployment configurations for production and preview environments. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentConfigs { + /// Production deployment configuration + pub production: Option, + /// Preview deployment configuration + pub preview: Option, +} + +/// Pages Source Configuration +/// +/// Git repository configuration for a Pages project. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesSource { + /// Repository type (e.g., "github") + #[serde(rename = "type")] + pub source_type: String, + /// Configuration details for the source + pub config: HashMap, +} + +/// Pages Project +/// +/// Represents a Cloudflare Pages project. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesProject { + /// Project identifier + pub id: String, + /// Project name + pub name: String, + /// Project subdomain (e.g., "projectname.pages.dev") + pub subdomain: String, + /// Production branch of the project (used to identify production deployments) + pub production_branch: Option, + /// List of associated custom domains (optional) + pub domains: Option>, + /// Source configuration (optional) + pub source: Option, + /// Build configuration (optional) + pub build_config: Option, + /// Deployment configurations for different environments (optional) + pub deployment_configs: Option, + /// Most recent deployment to the repo (optional) + pub latest_deployment: Option, + /// Most recent deployment to production (optional) + pub canonical_deployment: Option, + /// When the project was created + pub created_on: String, + /// When the project was last modified (optional) + pub modified_on: Option, +} + +impl ApiResult for PagesProject {} +impl ApiResult for Vec {} + +/// Pages Domain +/// +/// Represents a custom domain associated with a Pages project. +/// +/// +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDomain { + /// Domain identifier + pub id: String, + /// Domain name + pub name: String, + /// Domain status + pub status: String, + /// Verification token (optional) + pub verification_token: Option, + /// When the domain was created + pub created_on: String, + /// When the domain was last modified (optional) + pub modified_on: Option, +} + +impl ApiResult for PagesDomain {} +impl ApiResult for Vec {} + +/// Pages Deployment Logs +/// +/// Build and deployment logs for a Pages deployment. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesDeploymentLogs { + /// Log lines + pub data: Vec>, + /// Total log count + pub total: Option, + /// Whether this includes all logs + pub includes_container_logs: Option, +} + +impl ApiResult for PagesDeploymentLogs {} + +/// Pages Build Cache Purge Result +/// +/// Result of purging the build cache for a Pages project. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct PagesBuildCachePurgeResult { + /// Whether the purge was successful + pub purged: bool, +} + +impl ApiResult for PagesBuildCachePurgeResult {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pages_project_deserialization() { + let json = r#" + { + "id": "23cef5d4-8b9a-4b0a-9b1a-2b3c4d5e6f7g", + "name": "my-project", + "subdomain": "my-project.pages.dev", + "domains": ["example.com"], + "created_on": "2024-01-01T00:00:00.000Z", + "latest_deployment": { + "id": "deployment-123", + "short_id": "abc123", + "project_id": "23cef5d4-8b9a-4b0a-9b1a-2b3c4d5e6f7g", + "project_name": "my-project", + "environment": "production", + "url": "https://my-project.pages.dev", + "created_on": "2024-01-01T00:00:00.000Z" + } + } + "#; + + let project: PagesProject = serde_json::from_str(json).unwrap(); + assert_eq!(project.id, "23cef5d4-8b9a-4b0a-9b1a-2b3c4d5e6f7g"); + assert_eq!(project.name, "my-project"); + assert_eq!(project.subdomain, "my-project.pages.dev"); + assert_eq!(project.domains, Some(vec!["example.com".to_string()])); + assert!(project.latest_deployment.is_some()); + } + + #[test] + fn test_pages_deployment_deserialization() { + let json = r#" + { + "id": "deployment-123", + "short_id": "abc123", + "project_id": "project-456", + "project_name": "my-project", + "environment": "production", + "url": "https://abc123.my-project.pages.dev", + "created_on": "2024-01-01T00:00:00.000Z", + "aliases": ["https://my-project.pages.dev"], + "deployment_trigger": { + "type": "push", + "metadata": { + "branch": "main", + "commit_hash": "abc123def456", + "commit_message": "Update homepage" + } + } + } + "#; + + let deployment: PagesDeployment = serde_json::from_str(json).unwrap(); + assert_eq!(deployment.id, "deployment-123"); + assert_eq!(deployment.short_id, "abc123"); + assert_eq!(deployment.environment, "production"); + assert_eq!(deployment.aliases, Some(vec!["https://my-project.pages.dev".to_string()])); + + let trigger = deployment.deployment_trigger.unwrap(); + assert_eq!(trigger.trigger_type, PagesDeploymentTriggerType::Push); + + let metadata = trigger.metadata.unwrap(); + assert_eq!(metadata.branch, Some("main".to_string())); + assert_eq!(metadata.commit_hash, Some("abc123def456".to_string())); + } + + #[test] + fn test_pages_build_config_default() { + let config = PagesBuildConfig::default(); + assert_eq!(config.build_caching, None); + assert_eq!(config.build_command, None); + assert_eq!(config.destination_dir, None); + assert_eq!(config.root_dir, None); + } + + #[test] + fn test_pages_domain_deserialization() { + let json = r#" + { + "id": "domain-123", + "name": "example.com", + "status": "active", + "verification_token": "token123", + "created_on": "2024-01-01T00:00:00.000Z" + } + "#; + + let domain: PagesDomain = serde_json::from_str(json).unwrap(); + assert_eq!(domain.id, "domain-123"); + assert_eq!(domain.name, "example.com"); + assert_eq!(domain.status, "active"); + assert_eq!(domain.verification_token, Some("token123".to_string())); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/delete_deployment.rs b/cloudflare/src/endpoints/pages/delete_deployment.rs new file mode 100644 index 00000000..f704a381 --- /dev/null +++ b/cloudflare/src/endpoints/pages/delete_deployment.rs @@ -0,0 +1,44 @@ +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Delete a Pages deployment +/// +/// Deletes a specific deployment by ID. This action cannot be undone. +/// +/// +#[derive(Debug)] +pub struct DeleteDeployment<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub deployment_id: &'a str, +} + +impl<'a> DeleteDeployment<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + deployment_id: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + deployment_id, + } + } +} + +impl EndpointSpec for DeleteDeployment<'_> { + type JsonResponse = (); + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::DELETE + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments/{}", + self.account_identifier, self.project_name, self.deployment_id + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/delete_domain.rs b/cloudflare/src/endpoints/pages/delete_domain.rs new file mode 100644 index 00000000..691c2170 --- /dev/null +++ b/cloudflare/src/endpoints/pages/delete_domain.rs @@ -0,0 +1,44 @@ +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Delete a custom domain from a Pages project +/// +/// Removes a custom domain association from a Pages project. +/// +/// +#[derive(Debug)] +pub struct DeleteDomain<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub domain_name: &'a str, +} + +impl<'a> DeleteDomain<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + domain_name: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + domain_name, + } + } +} + +impl EndpointSpec for DeleteDomain<'_> { + type JsonResponse = (); + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::DELETE + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/domains/{}", + self.account_identifier, self.project_name, self.domain_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/delete_project.rs b/cloudflare/src/endpoints/pages/delete_project.rs new file mode 100644 index 00000000..831b6907 --- /dev/null +++ b/cloudflare/src/endpoints/pages/delete_project.rs @@ -0,0 +1,38 @@ +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Delete a Pages project +/// +/// Permanently deletes a Pages project by name. This action cannot be undone. +/// +/// +#[derive(Debug)] +pub struct DeleteProject<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, +} + +impl<'a> DeleteProject<'a> { + pub fn new(account_identifier: &'a str, project_name: &'a str) -> Self { + Self { + account_identifier, + project_name, + } + } +} + +impl EndpointSpec for DeleteProject<'_> { + type JsonResponse = (); + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::DELETE + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}", + self.account_identifier, self.project_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/get_deployment.rs b/cloudflare/src/endpoints/pages/get_deployment.rs new file mode 100644 index 00000000..33bb22ae --- /dev/null +++ b/cloudflare/src/endpoints/pages/get_deployment.rs @@ -0,0 +1,46 @@ +use super::data_structures::PagesDeployment; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get a specific deployment for a Pages project +/// +/// Fetches details of a specific deployment by ID. +/// +/// +#[derive(Debug)] +pub struct GetDeployment<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub deployment_id: &'a str, +} + +impl<'a> GetDeployment<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + deployment_id: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + deployment_id, + } + } +} + +impl EndpointSpec for GetDeployment<'_> { + type JsonResponse = PagesDeployment; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments/{}", + self.account_identifier, self.project_name, self.deployment_id + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/get_deployment_logs.rs b/cloudflare/src/endpoints/pages/get_deployment_logs.rs new file mode 100644 index 00000000..139ef737 --- /dev/null +++ b/cloudflare/src/endpoints/pages/get_deployment_logs.rs @@ -0,0 +1,46 @@ +use super::data_structures::PagesDeploymentLogs; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get deployment logs for a Pages deployment +/// +/// Fetches build and deployment logs for a specific deployment. +/// +/// +#[derive(Debug)] +pub struct GetDeploymentLogs<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub deployment_id: &'a str, +} + +impl<'a> GetDeploymentLogs<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + deployment_id: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + deployment_id, + } + } +} + +impl EndpointSpec for GetDeploymentLogs<'_> { + type JsonResponse = PagesDeploymentLogs; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments/{}/history/logs", + self.account_identifier, self.project_name, self.deployment_id + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/get_domain.rs b/cloudflare/src/endpoints/pages/get_domain.rs new file mode 100644 index 00000000..5436e9df --- /dev/null +++ b/cloudflare/src/endpoints/pages/get_domain.rs @@ -0,0 +1,46 @@ +use super::data_structures::PagesDomain; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get a specific domain for a Pages project +/// +/// Fetches details of a specific custom domain by name. +/// +/// +#[derive(Debug)] +pub struct GetDomain<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub domain_name: &'a str, +} + +impl<'a> GetDomain<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + domain_name: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + domain_name, + } + } +} + +impl EndpointSpec for GetDomain<'_> { + type JsonResponse = PagesDomain; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/domains/{}", + self.account_identifier, self.project_name, self.domain_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/get_project.rs b/cloudflare/src/endpoints/pages/get_project.rs new file mode 100644 index 00000000..e98ed6ad --- /dev/null +++ b/cloudflare/src/endpoints/pages/get_project.rs @@ -0,0 +1,40 @@ +use super::data_structures::PagesProject; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Get a Pages project by name +/// +/// Fetches details of a specific Pages project by name. +/// +/// +#[derive(Debug)] +pub struct GetProject<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, +} + +impl<'a> GetProject<'a> { + pub fn new(account_identifier: &'a str, project_name: &'a str) -> Self { + Self { + account_identifier, + project_name, + } + } +} + +impl EndpointSpec for GetProject<'_> { + type JsonResponse = PagesProject; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}", + self.account_identifier, self.project_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/list_deployments.rs b/cloudflare/src/endpoints/pages/list_deployments.rs new file mode 100644 index 00000000..22a79654 --- /dev/null +++ b/cloudflare/src/endpoints/pages/list_deployments.rs @@ -0,0 +1,40 @@ +use super::data_structures::PagesDeployment; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List deployments for a Pages project +/// +/// Fetches a list of all deployments for a specific Pages project. +/// +/// +#[derive(Debug)] +pub struct ListDeployments<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, +} + +impl<'a> ListDeployments<'a> { + pub fn new(account_identifier: &'a str, project_name: &'a str) -> Self { + Self { + account_identifier, + project_name, + } + } +} + +impl EndpointSpec for ListDeployments<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments", + self.account_identifier, self.project_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/list_domains.rs b/cloudflare/src/endpoints/pages/list_domains.rs new file mode 100644 index 00000000..fafb965f --- /dev/null +++ b/cloudflare/src/endpoints/pages/list_domains.rs @@ -0,0 +1,40 @@ +use super::data_structures::PagesDomain; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List domains for a Pages project +/// +/// Fetches a list of all custom domains associated with a Pages project. +/// +/// +#[derive(Debug)] +pub struct ListDomains<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, +} + +impl<'a> ListDomains<'a> { + pub fn new(account_identifier: &'a str, project_name: &'a str) -> Self { + Self { + account_identifier, + project_name, + } + } +} + +impl EndpointSpec for ListDomains<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/domains", + self.account_identifier, self.project_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/list_projects.rs b/cloudflare/src/endpoints/pages/list_projects.rs new file mode 100644 index 00000000..8e578a63 --- /dev/null +++ b/cloudflare/src/endpoints/pages/list_projects.rs @@ -0,0 +1,35 @@ +use super::data_structures::PagesProject; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// List all Pages projects in an account +/// +/// Fetches a list of all Pages projects owned by the account. +/// +/// +#[derive(Debug)] +pub struct ListProjects<'a> { + pub account_identifier: &'a str, +} + +impl<'a> ListProjects<'a> { + pub fn new(account_identifier: &'a str) -> Self { + Self { + account_identifier, + } + } +} + +impl EndpointSpec for ListProjects<'_> { + type JsonResponse = Vec; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::GET + } + + fn path(&self) -> String { + format!("accounts/{}/pages/projects", self.account_identifier) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/mod.rs b/cloudflare/src/endpoints/pages/mod.rs new file mode 100644 index 00000000..88607aaf --- /dev/null +++ b/cloudflare/src/endpoints/pages/mod.rs @@ -0,0 +1,62 @@ +/*! +Cloudflare Pages endpoints for static site hosting and deployment. + +This module provides comprehensive Pages project and deployment management capabilities including: +- Project CRUD operations (create, read, update, delete) +- Deployment management (create, list, retry, rollback) +- Custom domain management +- Build cache operations + +All endpoints are fully compliant with the official Cloudflare API specification. +*/ + +pub mod create_project; +pub mod data_structures; +pub mod delete_project; +pub mod get_project; +pub mod list_projects; +pub mod update_project; + +pub mod add_domain; +pub mod delete_domain; +pub mod get_domain; +pub mod list_domains; +pub mod update_domain; + +pub mod create_deployment; +pub mod delete_deployment; +pub mod get_deployment; +pub mod get_deployment_logs; +pub mod list_deployments; +pub mod retry_deployment; +pub mod rollback_deployment; + +pub mod purge_build_cache; + +pub use create_project::{CreateProject, CreateProjectParams}; +pub use data_structures::{ + PagesBuildConfig, PagesDeployment, PagesDeploymentConfig, PagesDeploymentConfigs, + PagesDeploymentLogs, PagesDeploymentStage, PagesDeploymentTrigger, + PagesDeploymentTriggerMetadata, PagesDeploymentTriggerType, PagesDomain, PagesProject, + PagesSource, PagesBuildCachePurgeResult, +}; +pub use delete_project::DeleteProject; +pub use get_project::GetProject; +pub use list_projects::ListProjects; +pub use update_project::{UpdateProject, UpdateProjectParams}; + +pub use add_domain::{AddDomain, AddDomainParams}; +pub use delete_domain::DeleteDomain; +pub use get_domain::GetDomain; +pub use list_domains::ListDomains; +pub use update_domain::{UpdateDomain, UpdateDomainParams}; + +pub use create_deployment::{CreateDeployment, CreateDeploymentParams}; +pub use delete_deployment::DeleteDeployment; +pub use get_deployment::GetDeployment; +pub use get_deployment_logs::GetDeploymentLogs; +pub use list_deployments::ListDeployments; +pub use retry_deployment::RetryDeployment; +pub use rollback_deployment::RollbackDeployment; + +pub use purge_build_cache::PurgeBuildCache; \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/purge_build_cache.rs b/cloudflare/src/endpoints/pages/purge_build_cache.rs new file mode 100644 index 00000000..16c29631 --- /dev/null +++ b/cloudflare/src/endpoints/pages/purge_build_cache.rs @@ -0,0 +1,41 @@ +use super::data_structures::PagesBuildCachePurgeResult; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Purge build cache for a Pages project +/// +/// Purges all cached build artifacts for a Pages project, forcing the next +/// deployment to rebuild from scratch. +/// +/// +#[derive(Debug)] +pub struct PurgeBuildCache<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, +} + +impl<'a> PurgeBuildCache<'a> { + pub fn new(account_identifier: &'a str, project_name: &'a str) -> Self { + Self { + account_identifier, + project_name, + } + } +} + +impl EndpointSpec for PurgeBuildCache<'_> { + type JsonResponse = PagesBuildCachePurgeResult; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/purge_build_cache", + self.account_identifier, self.project_name + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/retry_deployment.rs b/cloudflare/src/endpoints/pages/retry_deployment.rs new file mode 100644 index 00000000..46bcd4e4 --- /dev/null +++ b/cloudflare/src/endpoints/pages/retry_deployment.rs @@ -0,0 +1,46 @@ +use super::data_structures::PagesDeployment; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Retry a failed Pages deployment +/// +/// Retries a previous deployment that failed during the build or deploy process. +/// +/// +#[derive(Debug)] +pub struct RetryDeployment<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub deployment_id: &'a str, +} + +impl<'a> RetryDeployment<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + deployment_id: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + deployment_id, + } + } +} + +impl EndpointSpec for RetryDeployment<'_> { + type JsonResponse = PagesDeployment; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments/{}/retry", + self.account_identifier, self.project_name, self.deployment_id + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/rollback_deployment.rs b/cloudflare/src/endpoints/pages/rollback_deployment.rs new file mode 100644 index 00000000..974c5b10 --- /dev/null +++ b/cloudflare/src/endpoints/pages/rollback_deployment.rs @@ -0,0 +1,47 @@ +use super::data_structures::PagesDeployment; + +use crate::framework::endpoint::{EndpointSpec, Method}; +use crate::framework::response::ApiSuccess; + +/// Rollback to a previous Pages deployment +/// +/// Rollbacks the production deployment to a previous successful deployment. +/// You can only rollback to successful builds on production. +/// +/// +#[derive(Debug)] +pub struct RollbackDeployment<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub deployment_id: &'a str, +} + +impl<'a> RollbackDeployment<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + deployment_id: &'a str, + ) -> Self { + Self { + account_identifier, + project_name, + deployment_id, + } + } +} + +impl EndpointSpec for RollbackDeployment<'_> { + type JsonResponse = PagesDeployment; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::POST + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/deployments/{}/rollback", + self.account_identifier, self.project_name, self.deployment_id + ) + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/update_domain.rs b/cloudflare/src/endpoints/pages/update_domain.rs new file mode 100644 index 00000000..653cdd05 --- /dev/null +++ b/cloudflare/src/endpoints/pages/update_domain.rs @@ -0,0 +1,107 @@ +use super::data_structures::PagesDomain; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Update a custom domain for a Pages project +/// +/// Updates the configuration of a custom domain associated with a Pages project. +/// +/// +#[derive(Debug)] +pub struct UpdateDomain<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub domain_name: &'a str, + pub params: UpdateDomainParams, +} + +impl<'a> UpdateDomain<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + domain_name: &'a str, + params: UpdateDomainParams, + ) -> Self { + Self { + account_identifier, + project_name, + domain_name, + params, + } + } +} + +impl EndpointSpec for UpdateDomain<'_> { + type JsonResponse = PagesDomain; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PATCH + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}/domains/{}", + self.account_identifier, self.project_name, self.domain_name + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for updating a domain +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct UpdateDomainParams { + /// Updated domain name (optional) + pub name: Option, +} + +impl UpdateDomainParams { + /// Create empty update parameters + pub fn new() -> Self { + Self { name: None } + } + + /// Update the domain name + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } +} + +impl Default for UpdateDomainParams { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_update_domain_params_empty() { + let params = UpdateDomainParams::new(); + assert_eq!(params.name, None); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = "{}"; + assert_eq!(json, expected); + } + + #[test] + fn test_update_domain_params_with_name() { + let params = UpdateDomainParams::new().with_name("newdomain.com".to_string()); + assert_eq!(params.name, Some("newdomain.com".to_string())); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"newdomain.com"}"#; + assert_eq!(json, expected); + } +} \ No newline at end of file diff --git a/cloudflare/src/endpoints/pages/update_project.rs b/cloudflare/src/endpoints/pages/update_project.rs new file mode 100644 index 00000000..40909846 --- /dev/null +++ b/cloudflare/src/endpoints/pages/update_project.rs @@ -0,0 +1,185 @@ +use super::data_structures::{PagesBuildConfig, PagesDeploymentConfigs, PagesProject, PagesSource}; + +use crate::framework::endpoint::{EndpointSpec, Method, RequestBody}; +use crate::framework::response::ApiSuccess; +use serde::Serialize; + +/// Update an existing Pages project +/// +/// Updates the configuration of an existing Pages project. +/// +/// +#[derive(Debug)] +pub struct UpdateProject<'a> { + pub account_identifier: &'a str, + pub project_name: &'a str, + pub params: UpdateProjectParams, +} + +impl<'a> UpdateProject<'a> { + pub fn new( + account_identifier: &'a str, + project_name: &'a str, + params: UpdateProjectParams, + ) -> Self { + Self { + account_identifier, + project_name, + params, + } + } +} + +impl EndpointSpec for UpdateProject<'_> { + type JsonResponse = PagesProject; + type ResponseType = ApiSuccess; + + fn method(&self) -> Method { + Method::PATCH + } + + fn path(&self) -> String { + format!( + "accounts/{}/pages/projects/{}", + self.account_identifier, self.project_name + ) + } + + fn body(&self) -> Option { + let body = serde_json::to_string(&self.params).unwrap(); + Some(RequestBody::Json(body)) + } +} + +/// Parameters for updating a Pages project +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Debug, PartialEq, Eq)] +pub struct UpdateProjectParams { + /// Updated name for the project (optional) + pub name: Option, + /// Updated subdomain for the project (optional) + pub subdomain: Option, + /// Updated production branch for the project (optional) + pub production_branch: Option, + /// Updated list of custom domains (optional) + pub domains: Option>, + /// Updated source configuration (optional) + pub source: Option, + /// Updated build configuration (optional) + pub build_config: Option, + /// Updated deployment configurations (optional) + pub deployment_configs: Option, +} + +impl UpdateProjectParams { + /// Create empty update parameters + pub fn new() -> Self { + Self { + name: None, + subdomain: None, + production_branch: None, + domains: None, + source: None, + build_config: None, + deployment_configs: None, + } + } + + /// Update the project name + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + /// Update the project subdomain + pub fn with_subdomain(mut self, subdomain: String) -> Self { + self.subdomain = Some(subdomain); + self + } + + /// Update the production branch + pub fn with_production_branch(mut self, production_branch: String) -> Self { + self.production_branch = Some(production_branch); + self + } + + /// Update the custom domains + pub fn with_domains(mut self, domains: Vec) -> Self { + self.domains = Some(domains); + self + } + + /// Update the source configuration + pub fn with_source(mut self, source: PagesSource) -> Self { + self.source = Some(source); + self + } + + /// Update the build configuration + pub fn with_build_config(mut self, build_config: PagesBuildConfig) -> Self { + self.build_config = Some(build_config); + self + } + + /// Update the deployment configurations + pub fn with_deployment_configs(mut self, deployment_configs: PagesDeploymentConfigs) -> Self { + self.deployment_configs = Some(deployment_configs); + self + } +} + +impl Default for UpdateProjectParams { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_update_project_params_empty() { + let params = UpdateProjectParams::new(); + assert_eq!(params.name, None); + assert_eq!(params.subdomain, None); + assert_eq!(params.domains, None); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = "{}"; + assert_eq!(json, expected); + } + + #[test] + fn test_update_project_params_with_name() { + let params = UpdateProjectParams::new().with_name("updated-project".to_string()); + assert_eq!(params.name, Some("updated-project".to_string())); + + let json = serde_json::to_string(¶ms).unwrap(); + let expected = r#"{"name":"updated-project"}"#; + assert_eq!(json, expected); + } + + #[test] + fn test_update_project_params_with_domains() { + let params = UpdateProjectParams::new() + .with_domains(vec!["newdomain.com".to_string(), "www.newdomain.com".to_string()]); + + assert_eq!( + params.domains, + Some(vec!["newdomain.com".to_string(), "www.newdomain.com".to_string()]) + ); + } + + #[test] + fn test_update_project_params_chaining() { + let params = UpdateProjectParams::new() + .with_name("updated-project".to_string()) + .with_subdomain("new-subdomain".to_string()) + .with_domains(vec!["example.com".to_string()]); + + assert_eq!(params.name, Some("updated-project".to_string())); + assert_eq!(params.subdomain, Some("new-subdomain".to_string())); + assert_eq!(params.domains, Some(vec!["example.com".to_string()])); + } +} \ No newline at end of file diff --git a/cloudflare/tests/pages_integration.rs b/cloudflare/tests/pages_integration.rs new file mode 100644 index 00000000..d43c0d8f --- /dev/null +++ b/cloudflare/tests/pages_integration.rs @@ -0,0 +1,104 @@ +use cloudflare::endpoints::pages::*; +use std::collections::HashMap; + +/// Test that Pages endpoints can be constructed successfully +#[test] +fn test_pages_endpoint_construction() { + let account_id = "test-account"; + let project_name = "test-project"; + let deployment_id = "test-deployment"; + let domain_name = "example.com"; + + // Test that all endpoints can be constructed without panicking + let _list_projects = ListProjects::new(account_id); + let _get_project = GetProject::new(account_id, project_name); + + let create_params = CreateProjectParams::new("my-project".to_string()); + let _create_project = CreateProject::new(account_id, create_params); + + let update_params = UpdateProjectParams::new(); + let _update_project = UpdateProject::new(account_id, project_name, update_params); + + let _delete_project = DeleteProject::new(account_id, project_name); + + // Test deployment endpoints + let _list_deployments = ListDeployments::new(account_id, project_name); + let _get_deployment = GetDeployment::new(account_id, project_name, deployment_id); + + let create_deployment_params = CreateDeploymentParams::new(); + let _create_deployment = CreateDeployment::new(account_id, project_name, create_deployment_params); + + let _retry_deployment = RetryDeployment::new(account_id, project_name, deployment_id); + let _rollback_deployment = RollbackDeployment::new(account_id, project_name, deployment_id); + + // Test domain endpoints + let _list_domains = ListDomains::new(account_id, project_name); + + let add_domain_params = AddDomainParams::new(domain_name.to_string()); + let _add_domain = AddDomain::new(account_id, project_name, add_domain_params); + + let _delete_domain = DeleteDomain::new(account_id, project_name, domain_name); + let _get_domain = GetDomain::new(account_id, project_name, domain_name); + + // Test utility endpoints + let _purge_build_cache = PurgeBuildCache::new(account_id, project_name); + let _get_deployment_logs = GetDeploymentLogs::new(account_id, project_name, deployment_id); +} + +#[test] +fn test_pages_parameter_builders() { + // Test CreateProjectParams builders + let basic_params = CreateProjectParams::new("my-project".to_string()); + assert_eq!(basic_params.name, "my-project"); + assert_eq!(basic_params.subdomain, None); + + let with_subdomain = CreateProjectParams::with_subdomain( + "my-project".to_string(), + "custom-subdomain".to_string(), + ); + assert_eq!(with_subdomain.subdomain, Some("custom-subdomain".to_string())); + + let with_domains = basic_params.clone() + .with_domains(vec!["example.com".to_string()]); + assert_eq!(with_domains.domains, Some(vec!["example.com".to_string()])); + + // Test Git source creation + let mut repo_config = HashMap::new(); + repo_config.insert("owner".to_string(), serde_json::Value::String("user".to_string())); + repo_config.insert("repo_name".to_string(), serde_json::Value::String("repo".to_string())); + + let with_git = CreateProjectParams::with_git_source( + "my-project".to_string(), + "github".to_string(), + repo_config.clone(), + ); + assert!(with_git.source.is_some()); + assert_eq!(with_git.source.unwrap().source_type, "github"); + + // Test UpdateProjectParams builders + let update_params = UpdateProjectParams::new() + .with_name("updated-name".to_string()) + .with_subdomain("new-subdomain".to_string()); + + assert_eq!(update_params.name, Some("updated-name".to_string())); + assert_eq!(update_params.subdomain, Some("new-subdomain".to_string())); + + // Test CreateDeploymentParams builders + let deployment_params = CreateDeploymentParams::from_branch("staging".to_string()); + assert_eq!(deployment_params.branch, Some("staging".to_string())); + + let mut env_vars = HashMap::new(); + env_vars.insert("NODE_ENV".to_string(), "production".to_string()); + + let with_env_vars = CreateDeploymentParams::new().with_env_vars(env_vars.clone()); + assert_eq!(with_env_vars.env_vars, Some(env_vars)); + + // Test production branch functionality + let with_prod_branch = CreateProjectParams::new("my-project".to_string()) + .with_production_branch("production".to_string()); + assert_eq!(with_prod_branch.production_branch, Some("production".to_string())); + + let update_prod_branch = UpdateProjectParams::new() + .with_production_branch("develop".to_string()); + assert_eq!(update_prod_branch.production_branch, Some("develop".to_string())); +} \ No newline at end of file