wrangler/commands/
subdomain.rs

1use 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            // list all the affected scripts
130            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}