From ca5b73bbb8409377392f4a56e8ebab253f6e0877 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 16 Jan 2024 16:20:33 -0500 Subject: [PATCH 1/3] Create cloudflare-derive-macros proc-macro crate --- Cargo.toml | 1 + cloudflare-derive-macros/Cargo.toml | 13 +++++++++++++ cloudflare-derive-macros/src/lib.rs | 29 +++++++++++++++++++++++++++++ cloudflare/Cargo.toml | 1 + 4 files changed, 44 insertions(+) create mode 100644 cloudflare-derive-macros/Cargo.toml create mode 100644 cloudflare-derive-macros/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d79a2375..e8f7bf96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "cloudflare", + "cloudflare-derive-macros", "cloudflare-examples", "cloudflare-e2e-test", ] \ No newline at end of file diff --git a/cloudflare-derive-macros/Cargo.toml b/cloudflare-derive-macros/Cargo.toml new file mode 100644 index 00000000..88e9573c --- /dev/null +++ b/cloudflare-derive-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cloudflare-derive-macros" +version = "0.1.0" +edition = "2018" +description = "Internal macros for the Cloudflare crate" +license = "BSD-3-Clause" + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" diff --git a/cloudflare-derive-macros/src/lib.rs b/cloudflare-derive-macros/src/lib.rs new file mode 100644 index 00000000..d1c5ca8f --- /dev/null +++ b/cloudflare-derive-macros/src/lib.rs @@ -0,0 +1,29 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(ApiResult)] +pub fn api_result_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let name = &ast.ident; + + let gen = quote! { + impl crate::framework::response::ApiResult for #name {} + }; + + gen.into() +} + +#[proc_macro_derive(VecApiResult)] +pub fn vec_api_result_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let name = &ast.ident; + + let gen = quote! { + impl crate::framework::response::ApiResult for Vec<#name> {} + }; + + gen.into() +} diff --git a/cloudflare/Cargo.toml b/cloudflare/Cargo.toml index b01b60d2..08426eab 100644 --- a/cloudflare/Cargo.toml +++ b/cloudflare/Cargo.toml @@ -33,3 +33,4 @@ serde_urlencoded = "0.7.1" thiserror = "1" url = "2.2" uuid = { version = "1.0", features = ["serde"] } +cloudflare-derive-macros = { path = "../cloudflare-derive-macros" } From 857e5022fb8cec46d050bca4e3d91e11f2481587 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 16 Jan 2024 16:43:05 -0500 Subject: [PATCH 2/3] Derive ApiResult and VecApiResult --- cloudflare/src/endpoints/account/mod.rs | 7 ++-- .../endpoints/argo_tunnel/data_structures.rs | 12 ++----- cloudflare/src/endpoints/dns.rs | 14 +++----- .../src/endpoints/load_balancing/delete_lb.rs | 5 ++- .../endpoints/load_balancing/delete_pool.rs | 5 ++- .../src/endpoints/load_balancing/list_lb.rs | 3 -- .../src/endpoints/load_balancing/mod.rs | 13 +++---- cloudflare/src/endpoints/r2.rs | 4 +-- cloudflare/src/endpoints/user.rs | 8 ++--- .../src/endpoints/workers/delete_script.rs | 5 ++- cloudflare/src/endpoints/workers/mod.rs | 36 ++++++++----------- cloudflare/src/endpoints/workerskv/mod.rs | 14 ++------ cloudflare/src/endpoints/zone.rs | 12 ++----- 13 files changed, 45 insertions(+), 93 deletions(-) diff --git a/cloudflare/src/endpoints/account/mod.rs b/cloudflare/src/endpoints/account/mod.rs index 2954079c..c3866141 100644 --- a/cloudflare/src/endpoints/account/mod.rs +++ b/cloudflare/src/endpoints/account/mod.rs @@ -1,5 +1,5 @@ -use crate::framework::response::ApiResult; use chrono::{DateTime, Utc}; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; pub mod list_accounts; @@ -8,7 +8,7 @@ pub use list_accounts::ListAccounts; /// Cloudflare Accounts /// An Account is the root object which owns other resources such as zones, load balancers and billing details. /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct Account { /// Account identifier tag. pub id: String, @@ -38,6 +38,3 @@ pub struct AccountDetails { /// Account name pub name: String, } - -impl ApiResult for Account {} -impl ApiResult for Vec {} diff --git a/cloudflare/src/endpoints/argo_tunnel/data_structures.rs b/cloudflare/src/endpoints/argo_tunnel/data_structures.rs index c6271519..0d4427de 100644 --- a/cloudflare/src/endpoints/argo_tunnel/data_structures.rs +++ b/cloudflare/src/endpoints/argo_tunnel/data_structures.rs @@ -1,13 +1,12 @@ use chrono::{offset::Utc, DateTime}; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::framework::response::ApiResult; - /// A Named Argo Tunnel /// This is an Argo Tunnel that has been created. It can be used for routing and subsequent running. /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct Tunnel { pub id: Uuid, pub created_at: DateTime, @@ -26,11 +25,8 @@ pub struct ActiveConnection { pub is_pending_reconnect: bool, } -impl ApiResult for Tunnel {} -impl ApiResult for Vec {} - /// The result of a route request for a Named Argo Tunnel -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] #[serde(untagged)] pub enum RouteResult { Dns(DnsRouteResult), @@ -55,5 +51,3 @@ pub enum Change { New, Updated, } - -impl ApiResult for RouteResult {} diff --git a/cloudflare/src/endpoints/dns.rs b/cloudflare/src/endpoints/dns.rs index 871945ff..fb26873b 100644 --- a/cloudflare/src/endpoints/dns.rs +++ b/cloudflare/src/endpoints/dns.rs @@ -1,11 +1,9 @@ -use crate::framework::{ - endpoint::{serialize_query, EndpointSpec, Method}, - response::ApiResult, -}; +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; /// use crate::framework::{OrderDirection, SearchMatch}; use chrono::offset::Utc; use chrono::DateTime; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -174,13 +172,13 @@ pub enum DnsContent { SRV { content: String }, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, ApiResult)] pub struct DeleteDnsRecordResponse { /// DNS record identifier tag pub id: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, ApiResult, VecApiResult)] pub struct DnsRecord { /// Extra Cloudflare-specific information about the record pub meta: Meta, @@ -208,7 +206,3 @@ pub struct DnsRecord { /// The domain of the record pub zone_name: String, } - -impl ApiResult for DnsRecord {} -impl ApiResult for Vec {} -impl ApiResult for DeleteDnsRecordResponse {} diff --git a/cloudflare/src/endpoints/load_balancing/delete_lb.rs b/cloudflare/src/endpoints/load_balancing/delete_lb.rs index 6723e0a5..548c05b1 100644 --- a/cloudflare/src/endpoints/load_balancing/delete_lb.rs +++ b/cloudflare/src/endpoints/load_balancing/delete_lb.rs @@ -1,6 +1,6 @@ use crate::framework::endpoint::{EndpointSpec, Method}; -use crate::framework::response::ApiResult; +use cloudflare_derive_macros::ApiResult; use serde::Deserialize; /// Delete Load Balancer @@ -25,8 +25,7 @@ impl<'a> EndpointSpec for DeleteLoadBalancer<'a> { } } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, ApiResult)] pub struct Response { pub id: String, } -impl ApiResult for Response {} diff --git a/cloudflare/src/endpoints/load_balancing/delete_pool.rs b/cloudflare/src/endpoints/load_balancing/delete_pool.rs index bae55a63..eeb1757b 100644 --- a/cloudflare/src/endpoints/load_balancing/delete_pool.rs +++ b/cloudflare/src/endpoints/load_balancing/delete_pool.rs @@ -1,6 +1,6 @@ use crate::framework::endpoint::{EndpointSpec, Method}; -use crate::framework::response::ApiResult; +use cloudflare_derive_macros::ApiResult; use serde::Deserialize; /// Delete Pool @@ -25,8 +25,7 @@ impl<'a> EndpointSpec for DeletePool<'a> { } } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, ApiResult)] pub struct Response { pub id: String, } -impl ApiResult for Response {} diff --git a/cloudflare/src/endpoints/load_balancing/list_lb.rs b/cloudflare/src/endpoints/load_balancing/list_lb.rs index 75066c59..406fe5db 100644 --- a/cloudflare/src/endpoints/load_balancing/list_lb.rs +++ b/cloudflare/src/endpoints/load_balancing/list_lb.rs @@ -1,6 +1,5 @@ use crate::endpoints::load_balancing::LoadBalancer; use crate::framework::endpoint::{EndpointSpec, Method}; -use crate::framework::response::ApiResult; /// List Load Balancers /// @@ -18,5 +17,3 @@ impl<'a> EndpointSpec> for ListLoadBalancers<'a> { format!("zones/{}/load_balancers", self.zone_identifier) } } - -impl ApiResult for Vec {} diff --git a/cloudflare/src/endpoints/load_balancing/mod.rs b/cloudflare/src/endpoints/load_balancing/mod.rs index 9b05a175..6233b2ad 100644 --- a/cloudflare/src/endpoints/load_balancing/mod.rs +++ b/cloudflare/src/endpoints/load_balancing/mod.rs @@ -5,15 +5,15 @@ pub mod delete_pool; pub mod list_lb; pub mod pool_details; -use crate::framework::response::ApiResult; use chrono::offset::Utc; use chrono::DateTime; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::net::IpAddr; -#[derive(Eq, PartialEq, Deserialize, Serialize, Clone, Debug)] +#[derive(Eq, PartialEq, Deserialize, Serialize, Clone, Debug, ApiResult, VecApiResult)] pub struct LoadBalancer { pub id: String, pub created_on: DateTime, @@ -108,8 +108,6 @@ pub enum Secure { Never, } -impl ApiResult for LoadBalancer {} - /// A pool is a set of origins that requests could be routed to (e.g. each of your data centers or /// regions have its own pool). /// Requests will be routed to particular pools according to your steering policy, and then balanced @@ -120,7 +118,7 @@ impl ApiResult for LoadBalancer {} /// handle. Then you might use a "dynamic latency" steering policy to ensure requests get routed /// to whatever pool can serve them fastest. So US users will probably get routed to the US pool. If /// the US pool becomes unavailable, they'll fail over to the Oceania pool. -#[derive(Eq, PartialEq, Deserialize, Serialize, Clone, Debug)] +#[derive(Eq, PartialEq, Deserialize, Serialize, Clone, Debug, ApiResult)] pub struct Pool { pub id: String, pub created_on: DateTime, @@ -151,7 +149,7 @@ pub struct Pool { /// An origin represents something that can serve user requests. Usually a machine, maybe an ELB. /// Origins with similar latency functions (e.g. origins in the same data center or region) might be /// in the same pool. -#[derive(Deserialize, Serialize, Clone, Debug)] +#[derive(Deserialize, Serialize, Clone, Debug, ApiResult)] pub struct Origin { /// A human-identifiable name for the origin. /// e.g. app-server-1 @@ -190,6 +188,3 @@ impl Hash for Origin { self.weight.to_bits().hash(state); } } - -impl ApiResult for Origin {} -impl ApiResult for Pool {} diff --git a/cloudflare/src/endpoints/r2.rs b/cloudflare/src/endpoints/r2.rs index 44626945..dfc7d16a 100644 --- a/cloudflare/src/endpoints/r2.rs +++ b/cloudflare/src/endpoints/r2.rs @@ -1,5 +1,6 @@ use chrono::offset::Utc; use chrono::DateTime; +use cloudflare_derive_macros::ApiResult; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -16,14 +17,13 @@ pub struct Bucket { } /// ListBucketsResult contains a list of buckets in an account. -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] pub struct ListBucketsResult { pub buckets: Vec, } type EmptyMap = HashMap<(), ()>; impl ApiResult for EmptyMap {} -impl ApiResult for ListBucketsResult {} /// Lists all buckets within the account. #[derive(Debug)] diff --git a/cloudflare/src/endpoints/user.rs b/cloudflare/src/endpoints/user.rs index 0d4113f1..20843558 100644 --- a/cloudflare/src/endpoints/user.rs +++ b/cloudflare/src/endpoints/user.rs @@ -1,7 +1,7 @@ use crate::framework::endpoint::{EndpointSpec, Method}; -use crate::framework::response::ApiResult; use chrono::{DateTime, Utc}; +use cloudflare_derive_macros::ApiResult; use serde::{Deserialize, Serialize}; /// Get User Details @@ -17,7 +17,7 @@ pub struct Organization { roles: Vec, // List of role names for the User at the Organization } -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] pub struct UserDetails { pub organizations: Vec, #[serde(default)] @@ -35,7 +35,6 @@ pub struct UserDetails { pub suspended: bool, pub email: String, } -impl ApiResult for UserDetails {} #[test] fn handles_empty_betas_field() { @@ -81,12 +80,11 @@ impl EndpointSpec for GetUserDetails { /// Returns whether a given token is valid or not. /// /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] pub struct UserTokenStatus { pub id: String, pub status: String, } -impl ApiResult for UserTokenStatus {} #[derive(Debug)] pub struct GetUserTokenStatus {} diff --git a/cloudflare/src/endpoints/workers/delete_script.rs b/cloudflare/src/endpoints/workers/delete_script.rs index e249d3a7..9a23f35d 100644 --- a/cloudflare/src/endpoints/workers/delete_script.rs +++ b/cloudflare/src/endpoints/workers/delete_script.rs @@ -1,6 +1,6 @@ use crate::framework::endpoint::{EndpointSpec, Method}; -use crate::framework::response::ApiResult; +use cloudflare_derive_macros::ApiResult; use serde::{Deserialize, Serialize}; /// Delete Workers script @@ -25,8 +25,7 @@ impl<'a> EndpointSpec for DeleteScript<'a> { } } -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] pub struct ScriptDeleteID { pub id: String, } -impl ApiResult for ScriptDeleteID {} diff --git a/cloudflare/src/endpoints/workers/mod.rs b/cloudflare/src/endpoints/workers/mod.rs index 2b590aae..edcf5b6e 100644 --- a/cloudflare/src/endpoints/workers/mod.rs +++ b/cloudflare/src/endpoints/workers/mod.rs @@ -1,6 +1,5 @@ -use crate::framework::response::ApiResult; - use chrono::{DateTime, Utc}; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; mod create_route; @@ -34,7 +33,7 @@ pub use send_tail_heartbeat::SendTailHeartbeat; /// Workers KV Route /// Routes are basic patterns used to enable or disable workers that match requests. /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct WorkersRoute { /// Namespace identifier tag. pub id: String, @@ -45,52 +44,47 @@ pub struct WorkersRoute { pub script: Option, } -impl ApiResult for WorkersRoute {} -impl ApiResult for Vec {} - /// A variant of WorkersRoute returned by the CreateRoute endpoint /// We could make `pattern` and `script` into `Option` types /// but it feels wrong. -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult)] pub struct WorkersRouteIdOnly { /// Namespace identifier tag. pub id: String, } -impl ApiResult for WorkersRouteIdOnly {} - /// Secrets attach to a single script to be readable in only the script /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive( + Deserialize, + Serialize, + Debug, + Clone, + PartialEq, + Eq, + ApiResult, + VecApiResult, /* to parse arrays too */ +)] pub struct WorkersSecret { pub name: String, #[serde(rename = "type")] pub secret_type: String, } -impl ApiResult for WorkersSecret {} -impl ApiResult for Vec {} // to parse arrays too - /// A Tail is attached to a single Worker and is impermanent /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct WorkersTail { pub id: String, pub url: Option, pub expires_at: DateTime, } -impl ApiResult for WorkersTail {} -impl ApiResult for Vec {} - // Binding for a Workers Script -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct WorkersBinding { pub name: String, pub r#type: String, pub namespace_id: String, pub class_name: Option, } - -impl ApiResult for WorkersBinding {} -impl ApiResult for Vec {} diff --git a/cloudflare/src/endpoints/workerskv/mod.rs b/cloudflare/src/endpoints/workerskv/mod.rs index 378fa2a6..76df42dc 100644 --- a/cloudflare/src/endpoints/workerskv/mod.rs +++ b/cloudflare/src/endpoints/workerskv/mod.rs @@ -1,6 +1,6 @@ -use crate::framework::response::ApiResult; use chrono::DateTime; use chrono::{TimeZone, Utc}; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; use serde::{Deserialize, Deserializer, Serialize}; @@ -41,7 +41,7 @@ const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &CONTROLS /// Workers KV Namespace /// A Namespace is a collection of key-value pairs stored in Workers KV. /// -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct WorkersKvNamespace { /// Namespace identifier tag. pub id: String, @@ -49,12 +49,8 @@ pub struct WorkersKvNamespace { pub title: String, } -impl ApiResult for WorkersKvNamespace {} - -impl ApiResult for Vec {} - #[serde_with::skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, ApiResult, VecApiResult)] pub struct Key { pub name: String, #[serde(default)] @@ -76,10 +72,6 @@ where Ok(None) } -impl ApiResult for Key {} - -impl ApiResult for Vec {} - fn url_encode_key(key: &str) -> String { percent_encode(key.as_bytes(), PATH_SEGMENT_ENCODE_SET).to_string() } diff --git a/cloudflare/src/endpoints/zone.rs b/cloudflare/src/endpoints/zone.rs index 1ab3f125..c6ca3b42 100644 --- a/cloudflare/src/endpoints/zone.rs +++ b/cloudflare/src/endpoints/zone.rs @@ -1,12 +1,10 @@ use crate::endpoints::{account::AccountDetails, plan::Plan}; use crate::framework::endpoint::serialize_query; -use crate::framework::{ - endpoint::{EndpointSpec, Method}, - response::ApiResult, -}; +use crate::framework::endpoint::{EndpointSpec, Method}; use crate::framework::{OrderDirection, SearchMatch}; use chrono::offset::Utc; use chrono::DateTime; +use cloudflare_derive_macros::{ApiResult, VecApiResult}; use serde::{Deserialize, Serialize}; /// List Zones @@ -142,7 +140,7 @@ pub struct Meta { /// A Zone is a domain name along with its subdomains and other identities /// -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, ApiResult, VecApiResult)] pub struct Zone { /// Zone identifier tag pub id: String, @@ -196,7 +194,3 @@ pub struct Zone { #[serde(rename = "type")] pub zone_type: Type, } - -// TODO: This should probably be a derive macro -impl ApiResult for Zone {} -impl ApiResult for Vec {} From adec9376864fabed0ae30598fe158463cfb06b50 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Mon, 22 Jan 2024 18:11:18 -0500 Subject: [PATCH 3/3] make cloudflare-derive-macros work externally --- cloudflare-derive-macros/src/lib.rs | 6 ++---- cloudflare/src/lib.rs | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cloudflare-derive-macros/src/lib.rs b/cloudflare-derive-macros/src/lib.rs index d1c5ca8f..c4307a1d 100644 --- a/cloudflare-derive-macros/src/lib.rs +++ b/cloudflare-derive-macros/src/lib.rs @@ -1,5 +1,3 @@ -extern crate proc_macro; - use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; @@ -10,7 +8,7 @@ pub fn api_result_derive(input: TokenStream) -> TokenStream { let name = &ast.ident; let gen = quote! { - impl crate::framework::response::ApiResult for #name {} + impl ::cloudflare::framework::response::ApiResult for #name {} }; gen.into() @@ -22,7 +20,7 @@ pub fn vec_api_result_derive(input: TokenStream) -> TokenStream { let name = &ast.ident; let gen = quote! { - impl crate::framework::response::ApiResult for Vec<#name> {} + impl ::cloudflare::framework::response::ApiResult for Vec<#name> {} }; gen.into() diff --git a/cloudflare/src/lib.rs b/cloudflare/src/lib.rs index 6518aa8c..61f7874a 100644 --- a/cloudflare/src/lib.rs +++ b/cloudflare/src/lib.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +extern crate self as cloudflare; pub mod endpoints; pub mod framework;