wrangler/commands/
subdomain.rs1use crate::http;
2use crate::settings::global_user::GlobalUser;
3use crate::settings::toml::Target;
4use crate::terminal::message::{Message, StdOut};
5use crate::terminal::{emoji, interactive};
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9
10#[derive(Serialize)]
11pub struct Subdomain {
12 subdomain: String,
13}
14
15impl Subdomain {
16 pub fn get(account_id: &str, user: &GlobalUser) -> Result<Option<String>> {
17 let addr = subdomain_addr(account_id);
18
19 let client = http::legacy_auth_client(user);
20
21 let response = client.get(&addr).send()?;
22
23 if !response.status().is_success() {
24 anyhow::bail!(
25 "{} There was an error fetching your subdomain.\n Status Code: {}\n Msg: {}",
26 emoji::WARN,
27 response.status(),
28 response.text()?,
29 )
30 }
31 let response: SubdomainResponse = serde_json::from_str(&response.text()?)?;
32 Ok(response.result.map(|r| r.subdomain))
33 }
34
35 pub fn put(name: &str, account_id: &str, user: &GlobalUser) -> Result<()> {
36 let addr = subdomain_addr(account_id);
37 let subdomain = Subdomain {
38 subdomain: name.to_string(),
39 };
40 let subdomain_request = serde_json::to_string(&subdomain)?;
41
42 let client = http::legacy_auth_client(user);
43
44 let response = client
45 .put(&addr)
46 .header("allow-rename", "1")
47 .body(subdomain_request)
48 .send()?;
49
50 let response_status = response.status();
51 if !response_status.is_success() {
52 let response_text = &response.text()?;
53 let r: SubdomainResponse = serde_json::from_str(response_text)?;
54 let api_error = r.errors.first().unwrap();
55 log::debug!("Status Code: {}", response_status);
56 log::debug!("Status Message: {}", response_text);
57 let msg = if response_status == 403 && api_error.code == 10031 {
58 format!(
59 "{} Your requested subdomain is not available. Please pick another one.",
60 emoji::WARN
61 )
62 } else {
63 format!(
64 "{} There was an error creating your requested subdomain.\n Status Code: {}\n Msg: {}",
65 emoji::WARN,
66 response_status,
67 response_text
68 )
69 };
70 anyhow::bail!(msg)
71 }
72 StdOut::success(&format!("Success! You've registered {}.", name));
73 Ok(())
74 }
75}
76
77#[derive(Deserialize)]
78struct SubdomainResponse {
79 result: Option<SubdomainResult>,
80 errors: Vec<Error>,
81}
82
83#[derive(Deserialize)]
84struct SubdomainResult {
85 subdomain: String,
86}
87
88#[derive(Deserialize)]
89struct ScriptResponse {
90 result: Vec<ScriptResult>,
91}
92
93#[derive(Deserialize)]
94struct ScriptResult {
95 id: String,
96 available_on_subdomain: bool,
97}
98
99#[derive(Deserialize)]
100struct Error {
101 code: i64,
102}
103
104fn subdomain_addr(account_id: &str) -> String {
105 format!(
106 "https://api.cloudflare.com/client/v4/accounts/{}/workers/subdomain",
107 account_id
108 )
109}
110
111fn register_subdomain(name: &str, user: &GlobalUser, target: &Target) -> Result<()> {
112 let msg = format!(
113 "Registering your subdomain, {}.workers.dev, this could take up to a minute.",
114 name
115 );
116 StdOut::working(&msg);
117 Subdomain::put(name, target.account_id.load()?, user)
118}
119
120pub fn set_subdomain(name: &str, user: &GlobalUser, target: &Target) -> Result<()> {
121 let account_id = target.account_id.load()?;
122 let subdomain = Subdomain::get(account_id, user)?;
123 if let Some(subdomain) = subdomain {
124 if subdomain == name {
125 let msg = format!("You have already registered {}.workers.dev", subdomain);
126 StdOut::success(&msg);
127 return Ok(());
128 } else {
129 let scripts = get_subdomain_scripts(account_id, user)?;
131
132 let default_msg = format!("Are you sure you want to permanently move your subdomain from {}.workers.dev to {}.workers.dev?",
133 subdomain, name);
134 let prompt_msg = if scripts.is_empty() {
135 default_msg
136 } else {
137 let mut script_updates: Vec<String> = Vec::new();
138 for script in scripts {
139 script_updates.push(format!(
140 "{}.{}.workers.dev => {}.{}.workers.dev",
141 script, subdomain, script, name
142 ))
143 }
144 let msg = format!(
145 "The following deployed Workers will be affected:\n{}\nIt may take a few minutes for these Workers to become available again.",
146 script_updates.join("\n")
147 );
148 format!("{}\n{}", msg, default_msg)
149 };
150
151 match interactive::confirm(&prompt_msg) {
152 Ok(true) => (),
153 Ok(false) => {
154 StdOut::info(&format!("Keeping subdomain: {}.workers.dev", subdomain));
155 return Ok(());
156 }
157 Err(e) => anyhow::bail!(e),
158 }
159 }
160 }
161
162 register_subdomain(name, user, target)
163}
164
165pub fn get_subdomain(user: &GlobalUser, target: &Target) -> Result<()> {
166 let subdomain = Subdomain::get(target.account_id.load()?, user)?;
167 if let Some(subdomain) = subdomain {
168 let msg = format!("{}.workers.dev", subdomain);
169 StdOut::info(&msg);
170 } else {
171 let msg =
172 "No subdomain registered. Use `wrangler subdomain <name>` to register one.".to_string();
173 StdOut::user_error(&msg);
174 }
175 Ok(())
176}
177
178fn get_subdomain_scripts(account_id: &str, user: &GlobalUser) -> Result<Vec<String>> {
179 let addr = scripts_addr(account_id);
180
181 let client = http::legacy_auth_client(user);
182
183 let response = client
184 .get(&addr)
185 .query(&[("include_subdomain_availability", "1")])
186 .send()?;
187
188 if !response.status().is_success() {
189 anyhow::bail!(
190 "{} There was an error fetching scripts.\n Status Code: {}\n Msg: {}",
191 emoji::WARN,
192 response.status(),
193 response.text()?,
194 )
195 }
196 let response: ScriptResponse = serde_json::from_str(&response.text()?)?;
197 let mut scripts: Vec<String> = Vec::new();
198 for script in response.result {
199 if script.available_on_subdomain {
200 scripts.push(script.id)
201 }
202 }
203 Ok(scripts)
204}
205
206fn scripts_addr(account_id: &str) -> String {
207 format!(
208 "https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts",
209 account_id
210 )
211}