Skip to main content

cargo_test_support/
registry.rs

1//! Interact with the [`TestRegistry`]
2//!
3//! # Example
4//!
5//! ```no_run
6//! use cargo_test_support::registry::Package;
7//! use cargo_test_support::project;
8//! use cargo_test_support::str;
9//!
10//! // Publish package "a" depending on "b".
11//! Package::new("a", "1.0.0")
12//!     .dep("b", "1.0.0")
13//!     .file("src/lib.rs", r#"
14//!         extern crate b;
15//!         pub fn f() -> i32 { b::f() * 2 }
16//!     "#)
17//!     .publish();
18//!
19//! // Publish package "b".
20//! Package::new("b", "1.0.0")
21//!     .file("src/lib.rs", r#"
22//!         pub fn f() -> i32 { 12 }
23//!     "#)
24//!     .publish();
25//!
26//! // Create a project that uses package "a".
27//! let p = project()
28//!     .file("Cargo.toml", r#"
29//!         [package]
30//!         name = "foo"
31//!         version = "0.0.1"
32//!
33//!         [dependencies]
34//!         a = "1.0"
35//!     "#)
36//!     .file("src/main.rs", r#"
37//!         extern crate a;
38//!         fn main() { println!("{}", a::f()); }
39//!     "#)
40//!     .build();
41//!
42//! // p.cargo("run").with_stdout_data(str!["24"]).run();
43//! ```
44
45use crate::git::repo;
46use crate::paths;
47use crate::publish::{create_index_line, write_to_index};
48use cargo_util::Sha256;
49use cargo_util::paths::append;
50use flate2::Compression;
51use flate2::write::GzEncoder;
52use std::collections::{BTreeMap, HashMap};
53use std::fmt;
54use std::fs::{self, File};
55use std::io::{BufRead, BufReader, Read, Write};
56use std::net::{SocketAddr, TcpListener, TcpStream};
57use std::path::{Path, PathBuf};
58use std::thread::{self, JoinHandle};
59use tar::{Builder, Header};
60use url::Url;
61
62/// Path to the local index for pseudo-crates.io.
63///
64/// This is a Git repo
65/// initialized with a `config.json` file pointing to `dl_path` for downloads
66/// and `api_path` for uploads.
67///
68/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/registry`
69pub fn registry_path() -> PathBuf {
70    generate_path("registry")
71}
72
73/// Path to the local web API uploads
74///
75/// Cargo will place the contents of a web API
76/// request here. For example, `api/v1/crates/new` is the result of publishing a crate.
77///
78/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/api`
79pub fn api_path() -> PathBuf {
80    generate_path("api")
81}
82
83/// Path to download `.crate` files using the web API endpoint.
84///
85/// Crates
86/// should be organized as `{name}/{version}/download` to match the web API
87/// endpoint. This is rarely used and must be manually set up.
88///
89/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/dl`
90pub fn dl_path() -> PathBuf {
91    generate_path("dl")
92}
93
94/// Path to the alternative-registry version of [`registry_path`]
95///
96/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-registry`
97pub fn alt_registry_path() -> PathBuf {
98    generate_path("alternative-registry")
99}
100
101/// URL to the alternative-registry version of `registry_url`
102fn alt_registry_url() -> Url {
103    generate_url("alternative-registry")
104}
105
106/// Path to the alternative-registry version of [`dl_path`]
107///
108/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-dl`
109pub fn alt_dl_path() -> PathBuf {
110    generate_path("alternative-dl")
111}
112
113/// Path to the alternative-registry version of [`api_path`]
114///
115/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/alternative-api`
116pub fn alt_api_path() -> PathBuf {
117    generate_path("alternative-api")
118}
119
120fn generate_path(name: &str) -> PathBuf {
121    paths::root().join(name)
122}
123fn generate_url(name: &str) -> Url {
124    Url::from_file_path(generate_path(name)).ok().unwrap()
125}
126
127type RequestCallback = Box<dyn Send + Fn(&Request, &HttpServer) -> Response>;
128
129/// Prepare a local [`TestRegistry`] fixture
130///
131/// See also [`init`] and [`alt_init`]
132pub struct RegistryBuilder {
133    /// If set, configures an alternate registry with the given name.
134    alternative: Option<String>,
135    /// The authorization token for the registry.
136    token: Option<String>,
137    /// If set, the registry requires authorization for all operations.
138    auth_required: bool,
139    /// If set, serves the index over http.
140    http_index: bool,
141    /// If set, serves the API over http.
142    http_api: bool,
143    /// If set, config.json includes 'api'
144    api: bool,
145    /// Write the token in the configuration.
146    configure_token: bool,
147    /// Write the registry in configuration.
148    configure_registry: bool,
149    /// API responders.
150    custom_responders: HashMap<String, RequestCallback>,
151    /// Handler for 404 responses.
152    not_found_handler: RequestCallback,
153    /// If nonzero, the git index update to be delayed by the given number of seconds.
154    delayed_index_update: usize,
155    /// Credential provider in configuration
156    credential_provider: Option<String>,
157}
158
159/// A local registry fixture
160///
161/// Most tests won't need to call this directly but instead interact with [`Package`]
162pub struct TestRegistry {
163    server: Option<HttpServerHandle>,
164    index_url: Url,
165    path: PathBuf,
166    api_url: Url,
167    dl_url: Url,
168    token: String,
169}
170
171impl TestRegistry {
172    pub fn index_url(&self) -> &Url {
173        &self.index_url
174    }
175
176    pub fn api_url(&self) -> &Url {
177        &self.api_url
178    }
179
180    pub fn token(&self) -> &str {
181        &self.token
182    }
183
184    /// Shutdown the server thread and wait for it to stop.
185    /// `Drop` automatically stops the server, but this additionally
186    /// waits for the thread to stop.
187    pub fn join(self) {
188        if let Some(mut server) = self.server {
189            server.stop();
190            let handle = server.handle.take().unwrap();
191            handle.join().unwrap();
192        }
193    }
194}
195
196impl RegistryBuilder {
197    #[must_use]
198    pub fn new() -> RegistryBuilder {
199        let not_found = |_req: &Request, _server: &HttpServer| -> Response {
200            Response {
201                code: 404,
202                headers: vec![],
203                body: b"not found".to_vec(),
204            }
205        };
206        RegistryBuilder {
207            alternative: None,
208            token: None,
209            auth_required: false,
210            http_api: false,
211            http_index: false,
212            api: true,
213            configure_registry: true,
214            configure_token: true,
215            custom_responders: HashMap::new(),
216            not_found_handler: Box::new(not_found),
217            delayed_index_update: 0,
218            credential_provider: None,
219        }
220    }
221
222    /// Adds a custom HTTP response for a specific url
223    #[must_use]
224    pub fn add_responder<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
225        mut self,
226        url: impl Into<String>,
227        responder: R,
228    ) -> Self {
229        self.custom_responders
230            .insert(url.into(), Box::new(responder));
231        self
232    }
233
234    #[must_use]
235    pub fn not_found_handler<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
236        mut self,
237        responder: R,
238    ) -> Self {
239        self.not_found_handler = Box::new(responder);
240        self
241    }
242
243    /// Configures the git index update to be delayed by the given number of seconds.
244    #[must_use]
245    pub fn delayed_index_update(mut self, delay: usize) -> Self {
246        self.delayed_index_update = delay;
247        self
248    }
249
250    /// Initializes as an alternative registry with the given name.
251    #[must_use]
252    pub fn alternative_named(mut self, alt: &str) -> Self {
253        self.alternative = Some(alt.to_string());
254        self
255    }
256
257    /// Initializes as an alternative registry named "alternative".
258    #[must_use]
259    pub fn alternative(self) -> Self {
260        self.alternative_named("alternative")
261    }
262
263    /// Prevents placing a token in the configuration
264    #[must_use]
265    pub fn no_configure_token(mut self) -> Self {
266        self.configure_token = false;
267        self
268    }
269
270    /// Prevents adding the registry to the configuration.
271    #[must_use]
272    pub fn no_configure_registry(mut self) -> Self {
273        self.configure_registry = false;
274        self
275    }
276
277    /// Sets the token value
278    #[must_use]
279    pub fn token(mut self, token: String) -> Self {
280        self.token = Some(token);
281        self
282    }
283
284    /// Sets this registry to require the authentication token for
285    /// all operations.
286    #[must_use]
287    pub fn auth_required(mut self) -> Self {
288        self.auth_required = true;
289        self
290    }
291
292    /// Operate the index over http
293    #[must_use]
294    pub fn http_index(mut self) -> Self {
295        self.http_index = true;
296        self
297    }
298
299    /// Operate the api over http
300    #[must_use]
301    pub fn http_api(mut self) -> Self {
302        self.http_api = true;
303        self
304    }
305
306    /// The registry has no api.
307    #[must_use]
308    pub fn no_api(mut self) -> Self {
309        self.api = false;
310        self
311    }
312
313    /// The credential provider to configure for this registry.
314    #[must_use]
315    pub fn credential_provider(mut self, provider: &[&str]) -> Self {
316        self.credential_provider = Some(format!("['{}']", provider.join("','")));
317        self
318    }
319
320    /// Initializes the registry.
321    #[must_use]
322    pub fn build(self) -> TestRegistry {
323        let config_path = paths::cargo_home().join("config.toml");
324        t!(fs::create_dir_all(config_path.parent().unwrap()));
325        let prefix = if let Some(alternative) = &self.alternative {
326            format!("{alternative}-")
327        } else {
328            String::new()
329        };
330        let registry_path = generate_path(&format!("{prefix}registry"));
331        let index_url = generate_url(&format!("{prefix}registry"));
332        let api_url = generate_url(&format!("{prefix}api"));
333        let dl_url = generate_url(&format!("{prefix}dl"));
334        let dl_path = generate_path(&format!("{prefix}dl"));
335        let api_path = generate_path(&format!("{prefix}api"));
336        let token = self.token.unwrap_or_else(|| format!("{prefix}sekrit"));
337
338        let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api {
339            // No need to start the HTTP server.
340            (None, index_url, api_url, dl_url)
341        } else {
342            let server = HttpServer::new(
343                registry_path.clone(),
344                dl_path,
345                api_path.clone(),
346                token.clone(),
347                self.auth_required,
348                self.custom_responders,
349                self.not_found_handler,
350                self.delayed_index_update,
351            );
352            let index_url = if self.http_index {
353                server.index_url()
354            } else {
355                index_url
356            };
357            let api_url = if self.http_api {
358                server.api_url()
359            } else {
360                api_url
361            };
362            let dl_url = server.dl_url();
363            (Some(server), index_url, api_url, dl_url)
364        };
365
366        let registry = TestRegistry {
367            api_url,
368            index_url,
369            server,
370            dl_url,
371            path: registry_path,
372            token,
373        };
374
375        if self.configure_registry {
376            if let Some(alternative) = &self.alternative {
377                append(
378                    &config_path,
379                    format!(
380                        "
381                        [registries.{alternative}]
382                        index = '{}'",
383                        registry.index_url
384                    )
385                    .as_bytes(),
386                )
387                .unwrap();
388                if let Some(p) = &self.credential_provider {
389                    append(
390                        &config_path,
391                        &format!(
392                            "
393                        credential-provider = {p}
394                        "
395                        )
396                        .as_bytes(),
397                    )
398                    .unwrap()
399                }
400            } else {
401                append(
402                    &config_path,
403                    format!(
404                        "
405                        [source.crates-io]
406                        replace-with = 'dummy-registry'
407
408                        [registries.dummy-registry]
409                        index = '{}'",
410                        registry.index_url
411                    )
412                    .as_bytes(),
413                )
414                .unwrap();
415
416                if let Some(p) = &self.credential_provider {
417                    append(
418                        &config_path,
419                        &format!(
420                            "
421                        [registry]
422                        credential-provider = {p}
423                        "
424                        )
425                        .as_bytes(),
426                    )
427                    .unwrap()
428                }
429            }
430        }
431
432        if self.configure_token {
433            let credentials = paths::cargo_home().join("credentials.toml");
434            let token = &registry.token;
435            if let Some(alternative) = &self.alternative {
436                append(
437                    &credentials,
438                    format!(
439                        r#"
440                            [registries.{alternative}]
441                            token = "{token}"
442                        "#
443                    )
444                    .as_bytes(),
445                )
446                .unwrap();
447            } else {
448                append(
449                    &credentials,
450                    format!(
451                        r#"
452                            [registry]
453                            token = "{token}"
454                        "#
455                    )
456                    .as_bytes(),
457                )
458                .unwrap();
459            }
460        }
461
462        let auth = if self.auth_required {
463            r#","auth-required":true"#
464        } else {
465            ""
466        };
467        let api = if self.api {
468            format!(r#","api":"{}""#, registry.api_url)
469        } else {
470            String::new()
471        };
472        // Initialize a new registry.
473        repo(&registry.path)
474            .file(
475                "config.json",
476                &format!(r#"{{"dl":"{}"{api}{auth}}}"#, registry.dl_url),
477            )
478            .build();
479        fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
480
481        registry
482    }
483}
484
485/// Published package builder for [`TestRegistry`]
486///
487/// This uses "source replacement" using an automatically generated
488/// `.cargo/config` file to ensure that dependencies will use these packages
489/// instead of contacting crates.io. See `source-replacement.md` for more
490/// details on how source replacement works.
491///
492/// Call [`Package::publish`] to finalize and create the package.
493///
494/// If no files are specified, an empty `lib.rs` file is automatically created.
495///
496/// The `Cargo.toml` file is automatically generated based on the methods
497/// called on `Package` (for example, calling [`Package::dep()`] will add to the
498/// `[dependencies]` automatically). You may also specify a `Cargo.toml` file
499/// to override the generated one.
500///
501/// This supports different registry types:
502/// - Regular source replacement that replaces `crates.io` (the default).
503/// - A "local registry" which is a subset for vendoring (see
504///   [`Package::local`]).
505/// - An "alternative registry" which requires specifying the registry name
506///   (see [`Package::alternative`]).
507///
508/// This does not support "directory sources". See `directory.rs` for
509/// `VendorPackage` which implements directory sources.
510#[must_use]
511pub struct Package {
512    name: String,
513    vers: String,
514    deps: Vec<Dependency>,
515    files: Vec<PackageFile>,
516    yanked: bool,
517    features: FeatureMap,
518    local: bool,
519    alternative: bool,
520    invalid_index_line: bool,
521    index_line: Option<String>,
522    edition: Option<String>,
523    resolver: Option<String>,
524    proc_macro: bool,
525    links: Option<String>,
526    rust_version: Option<String>,
527    cargo_features: Vec<String>,
528    pubtime: Option<String>,
529    v: Option<u32>,
530}
531
532pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
533
534/// Published package dependency builder, see [`Package::add_dep`]
535#[derive(Clone)]
536pub struct Dependency {
537    name: String,
538    vers: String,
539    kind: String,
540    artifact: Option<String>,
541    bindep_target: Option<String>,
542    lib: bool,
543    target: Option<String>,
544    features: Vec<String>,
545    registry: Option<String>,
546    package: Option<String>,
547    optional: bool,
548    default_features: bool,
549    public: bool,
550}
551
552/// Entry with data that corresponds to [`tar::EntryType`].
553#[non_exhaustive]
554enum EntryData {
555    Regular(String),
556    Symlink(PathBuf),
557    Directory,
558}
559
560/// A file to be created in a package.
561struct PackageFile {
562    path: String,
563    contents: EntryData,
564    /// The Unix mode for the file. Note that when extracted on Windows, this
565    /// is mostly ignored since it doesn't have the same style of permissions.
566    mode: u32,
567    /// If `true`, the file is created in the root of the tarfile, used for
568    /// testing invalid packages.
569    extra: bool,
570}
571
572const DEFAULT_MODE: u32 = 0o644;
573
574/// Setup a local pseudo-crates.io [`TestRegistry`]
575///
576/// This is implicitly called by [`Package::new`].
577///
578/// When calling `cargo publish`, see instead [`crate::publish`].
579pub fn init() -> TestRegistry {
580    RegistryBuilder::new().build()
581}
582
583/// Setup a local "alternative" [`TestRegistry`]
584///
585/// When calling `cargo publish`, see instead [`crate::publish`].
586pub fn alt_init() -> TestRegistry {
587    init();
588    RegistryBuilder::new().alternative().build()
589}
590
591pub struct HttpServerHandle {
592    addr: SocketAddr,
593    handle: Option<JoinHandle<()>>,
594}
595
596impl HttpServerHandle {
597    pub fn index_url(&self) -> Url {
598        Url::parse(&format!("sparse+http://{}/index/", self.addr)).unwrap()
599    }
600
601    pub fn api_url(&self) -> Url {
602        Url::parse(&format!("http://{}/", self.addr)).unwrap()
603    }
604
605    pub fn dl_url(&self) -> Url {
606        Url::parse(&format!("http://{}/dl", self.addr)).unwrap()
607    }
608
609    fn stop(&self) {
610        if let Ok(mut stream) = TcpStream::connect(self.addr) {
611            // shutdown the server
612            let _ = stream.write_all(b"stop");
613            let _ = stream.flush();
614        }
615    }
616}
617
618impl Drop for HttpServerHandle {
619    fn drop(&mut self) {
620        self.stop();
621    }
622}
623
624/// Request to the test http server
625#[derive(Clone)]
626pub struct Request {
627    pub url: Url,
628    pub method: String,
629    pub body: Option<Vec<u8>>,
630    pub authorization: Option<String>,
631    pub if_modified_since: Option<String>,
632    pub if_none_match: Option<String>,
633}
634
635impl fmt::Debug for Request {
636    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637        // body is not included as it can produce long debug outputs
638        f.debug_struct("Request")
639            .field("url", &self.url)
640            .field("method", &self.method)
641            .field("authorization", &self.authorization)
642            .field("if_modified_since", &self.if_modified_since)
643            .field("if_none_match", &self.if_none_match)
644            .finish()
645    }
646}
647
648/// Response from the test http server
649pub struct Response {
650    pub code: u32,
651    pub headers: Vec<String>,
652    pub body: Vec<u8>,
653}
654
655pub struct HttpServer {
656    listener: TcpListener,
657    registry_path: PathBuf,
658    dl_path: PathBuf,
659    api_path: PathBuf,
660    token: String,
661    auth_required: bool,
662    custom_responders: HashMap<String, RequestCallback>,
663    not_found_handler: RequestCallback,
664    delayed_index_update: usize,
665}
666
667/// A helper struct that collects the arguments for [`HttpServer::check_authorized`].
668/// Based on looking at the request, these are the fields that the authentication header should attest to.
669struct Mutation {}
670
671impl HttpServer {
672    pub fn new(
673        registry_path: PathBuf,
674        dl_path: PathBuf,
675        api_path: PathBuf,
676        token: String,
677        auth_required: bool,
678        custom_responders: HashMap<String, RequestCallback>,
679        not_found_handler: RequestCallback,
680        delayed_index_update: usize,
681    ) -> HttpServerHandle {
682        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
683        let addr = listener.local_addr().unwrap();
684        let server = HttpServer {
685            listener,
686            registry_path,
687            dl_path,
688            api_path,
689            token,
690            auth_required,
691            custom_responders,
692            not_found_handler,
693            delayed_index_update,
694        };
695        let handle = Some(thread::spawn(move || server.start()));
696        HttpServerHandle { addr, handle }
697    }
698
699    fn start(&self) {
700        let mut line = String::new();
701        'server: loop {
702            let (socket, _) = self.listener.accept().unwrap();
703            let mut buf = BufReader::new(socket);
704            line.clear();
705            if buf.read_line(&mut line).unwrap() == 0 {
706                // Connection terminated.
707                continue;
708            }
709            // Read the "GET path HTTP/1.1" line.
710            let mut parts = line.split_ascii_whitespace();
711            let method = parts.next().unwrap().to_ascii_lowercase();
712            if method == "stop" {
713                // Shutdown the server.
714                return;
715            }
716            let addr = self.listener.local_addr().unwrap();
717            let url = format!(
718                "http://{}/{}",
719                addr,
720                parts.next().unwrap().trim_start_matches('/')
721            );
722            let url = Url::parse(&url).unwrap();
723
724            // Grab headers we care about.
725            let mut if_modified_since = None;
726            let mut if_none_match = None;
727            let mut authorization = None;
728            let mut content_len = None;
729            loop {
730                line.clear();
731                if buf.read_line(&mut line).unwrap() == 0 {
732                    continue 'server;
733                }
734                if line == "\r\n" {
735                    // End of headers.
736                    line.clear();
737                    break;
738                }
739                let (name, value) = line.split_once(':').unwrap();
740                let name = name.trim().to_ascii_lowercase();
741                let value = value.trim().to_string();
742                match name.as_str() {
743                    "if-modified-since" => if_modified_since = Some(value),
744                    "if-none-match" => if_none_match = Some(value),
745                    "authorization" => authorization = Some(value),
746                    "content-length" => content_len = Some(value),
747                    _ => {}
748                }
749            }
750
751            let mut body = None;
752            if let Some(con_len) = content_len {
753                let len = con_len.parse::<u64>().unwrap();
754                let mut content = vec![0u8; len as usize];
755                buf.read_exact(&mut content).unwrap();
756                body = Some(content)
757            }
758
759            let req = Request {
760                authorization,
761                if_modified_since,
762                if_none_match,
763                method,
764                url,
765                body,
766            };
767            let response = self.route(&req);
768            let buf = buf.get_mut();
769            write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
770            write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
771            write!(buf, "Connection: close\r\n").unwrap();
772            for header in response.headers {
773                write!(buf, "{}\r\n", header).unwrap();
774            }
775            write!(buf, "\r\n").unwrap();
776            buf.write_all(&response.body).unwrap();
777            buf.flush().unwrap();
778        }
779    }
780
781    fn check_authorized(&self, req: &Request, mutation: Option<Mutation>) -> bool {
782        if mutation.is_some() || self.auth_required {
783            Some(&self.token) == req.authorization.as_ref()
784        } else {
785            assert!(req.authorization.is_none(), "unexpected token");
786            true
787        }
788    }
789
790    /// Route the request
791    fn route(&self, req: &Request) -> Response {
792        // Check for custom responder
793        if let Some(responder) = self.custom_responders.get(req.url.path()) {
794            return responder(&req, self);
795        }
796        let path: Vec<_> = req.url.path()[1..].split('/').collect();
797        match (req.method.as_str(), path.as_slice()) {
798            ("get", ["index", ..]) => {
799                if !self.check_authorized(req, None) {
800                    self.unauthorized(req)
801                } else {
802                    self.index(&req)
803                }
804            }
805            ("get", ["dl", ..]) => {
806                if !self.check_authorized(req, None) {
807                    self.unauthorized(req)
808                } else {
809                    self.dl(&req)
810                }
811            }
812            // publish
813            ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
814            // The remainder of the operators in the test framework do nothing other than responding 'ok'.
815            //
816            // Note: We don't need to support anything real here because there are no tests that
817            // currently require anything other than publishing via the http api.
818
819            // yank / unyank
820            ("delete" | "put", ["api", "v1", "crates", _, _, _]) => {
821                if !self.check_authorized(req, Some(Mutation {})) {
822                    self.unauthorized(req)
823                } else {
824                    self.ok(&req)
825                }
826            }
827            // owners
828            ("get" | "put" | "delete", ["api", "v1", "crates", _, "owners"]) => {
829                if !self.check_authorized(req, Some(Mutation {})) {
830                    self.unauthorized(req)
831                } else {
832                    self.ok(&req)
833                }
834            }
835            _ => self.not_found(&req),
836        }
837    }
838
839    /// Unauthorized response
840    pub fn unauthorized(&self, _req: &Request) -> Response {
841        Response {
842            code: 401,
843            headers: vec![
844                r#"www-authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
845            ],
846            body: b"Unauthorized message from server.".to_vec(),
847        }
848    }
849
850    /// Not found response
851    pub fn not_found(&self, req: &Request) -> Response {
852        (self.not_found_handler)(req, self)
853    }
854
855    /// Respond OK without doing anything
856    pub fn ok(&self, _req: &Request) -> Response {
857        Response {
858            code: 200,
859            headers: vec![],
860            body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
861        }
862    }
863
864    /// Return an internal server error (HTTP 500)
865    pub fn internal_server_error(&self, _req: &Request) -> Response {
866        Response {
867            code: 500,
868            headers: vec![],
869            body: br#"internal server error"#.to_vec(),
870        }
871    }
872
873    /// Return too many requests (HTTP 429)
874    pub fn too_many_requests(&self, _req: &Request, delay: std::time::Duration) -> Response {
875        Response {
876            code: 429,
877            headers: vec![format!("Retry-After: {}", delay.as_secs())],
878            body: format!(
879                "too many requests, try again in {} seconds",
880                delay.as_secs()
881            )
882            .into_bytes(),
883        }
884    }
885
886    /// Serve the download endpoint
887    pub fn dl(&self, req: &Request) -> Response {
888        let file = self
889            .dl_path
890            .join(req.url.path().strip_prefix("/dl/").unwrap());
891        println!("{}", file.display());
892        if !file.exists() {
893            return self.not_found(req);
894        }
895        return Response {
896            body: fs::read(&file).unwrap(),
897            code: 200,
898            headers: vec![],
899        };
900    }
901
902    /// Serve the registry index
903    pub fn index(&self, req: &Request) -> Response {
904        let file = self
905            .registry_path
906            .join(req.url.path().strip_prefix("/index/").unwrap());
907        if !file.exists() {
908            return self.not_found(req);
909        } else {
910            // Now grab info about the file.
911            let data = fs::read(&file).unwrap();
912            let etag = Sha256::new().update(&data).finish_hex();
913            let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
914
915            // Start to construct our response:
916            let mut any_match = false;
917            let mut all_match = true;
918            if let Some(expected) = &req.if_none_match {
919                if &etag != expected {
920                    all_match = false;
921                } else {
922                    any_match = true;
923                }
924            }
925            if let Some(expected) = &req.if_modified_since {
926                // NOTE: Equality comparison is good enough for tests.
927                if &last_modified != expected {
928                    all_match = false;
929                } else {
930                    any_match = true;
931                }
932            }
933
934            if any_match && all_match {
935                return Response {
936                    body: Vec::new(),
937                    code: 304,
938                    headers: vec![],
939                };
940            } else {
941                return Response {
942                    body: data,
943                    code: 200,
944                    headers: vec![
945                        format!("ETag: \"{}\"", etag),
946                        format!("Last-Modified: {}", last_modified),
947                    ],
948                };
949            }
950        }
951    }
952
953    pub fn check_authorized_publish(&self, req: &Request) -> Response {
954        if let Some(body) = &req.body {
955            // Mimic the publish behavior for local registries by writing out the request
956            // so tests can verify publishes made to either registry type.
957            let path = self.api_path.join("api/v1/crates/new");
958            t!(fs::create_dir_all(path.parent().unwrap()));
959            t!(fs::write(&path, body));
960
961            // Get the metadata of the package
962            let (len, remaining) = body.split_at(4);
963            let json_len = u32::from_le_bytes(len.try_into().unwrap());
964            let (json, remaining) = remaining.split_at(json_len as usize);
965            let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
966            // Get the `.crate` file
967            let (len, remaining) = remaining.split_at(4);
968            let file_len = u32::from_le_bytes(len.try_into().unwrap());
969            let (file, _remaining) = remaining.split_at(file_len as usize);
970            let file_cksum = cksum(&file);
971
972            if !self.check_authorized(req, Some(Mutation {})) {
973                return self.unauthorized(req);
974            }
975
976            let dst = self
977                .dl_path
978                .join(&new_crate.name)
979                .join(&new_crate.vers)
980                .join("download");
981
982            if self.delayed_index_update == 0 {
983                save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
984            } else {
985                let delayed_index_update = self.delayed_index_update;
986                let registry_path = self.registry_path.clone();
987                let file = Vec::from(file);
988                thread::spawn(move || {
989                    thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
990                    save_new_crate(dst, new_crate, &file, file_cksum, &registry_path);
991                });
992            }
993
994            self.ok(&req)
995        } else {
996            Response {
997                code: 400,
998                headers: vec![],
999                body: b"The request was missing a body".to_vec(),
1000            }
1001        }
1002    }
1003}
1004
1005fn save_new_crate(
1006    dst: PathBuf,
1007    new_crate: crates_io::NewCrate,
1008    file: &[u8],
1009    file_cksum: String,
1010    registry_path: &Path,
1011) {
1012    // Write the `.crate`
1013    t!(fs::create_dir_all(dst.parent().unwrap()));
1014    t!(fs::write(&dst, file));
1015
1016    let deps = new_crate
1017        .deps
1018        .iter()
1019        .map(|dep| {
1020            let (name, package) = match &dep.explicit_name_in_toml {
1021                Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1022                None => (dep.name.to_string(), None),
1023            };
1024            serde_json::json!({
1025                "name": name,
1026                "req": dep.version_req,
1027                "features": dep.features,
1028                "default_features": dep.default_features,
1029                "target": dep.target,
1030                "optional": dep.optional,
1031                "kind": dep.kind,
1032                "registry": dep.registry,
1033                "package": package,
1034                "artifact": dep.artifact,
1035                "bindep_target": dep.bindep_target,
1036                "lib": dep.lib,
1037            })
1038        })
1039        .collect::<Vec<_>>();
1040
1041    let line = create_index_line(
1042        serde_json::json!(new_crate.name),
1043        &new_crate.vers,
1044        deps,
1045        &file_cksum,
1046        new_crate.features,
1047        false,
1048        new_crate.links,
1049        new_crate.rust_version.as_deref(),
1050        None,
1051        None,
1052    );
1053
1054    write_to_index(registry_path, &new_crate.name, line, false);
1055}
1056
1057impl Package {
1058    /// Creates a new package builder.
1059    /// Call `publish()` to finalize and build the package.
1060    pub fn new(name: &str, vers: &str) -> Package {
1061        let config = paths::cargo_home().join("config.toml");
1062        if !config.exists() {
1063            init();
1064        }
1065        Package {
1066            name: name.to_string(),
1067            vers: vers.to_string(),
1068            deps: Vec::new(),
1069            files: Vec::new(),
1070            yanked: false,
1071            features: BTreeMap::new(),
1072            local: false,
1073            alternative: false,
1074            invalid_index_line: false,
1075            index_line: None,
1076            edition: None,
1077            resolver: None,
1078            proc_macro: false,
1079            links: None,
1080            rust_version: None,
1081            cargo_features: Vec::new(),
1082            pubtime: None,
1083            v: None,
1084        }
1085    }
1086
1087    /// Call with `true` to publish in a "local registry".
1088    ///
1089    /// See `source-replacement.html#local-registry-sources` for more details
1090    /// on local registries. See `local_registry.rs` for the tests that use
1091    /// this.
1092    pub fn local(&mut self, local: bool) -> &mut Package {
1093        self.local = local;
1094        self
1095    }
1096
1097    /// Call with `true` to publish in an "alternative registry".
1098    ///
1099    /// The name of the alternative registry is called "alternative".
1100    ///
1101    /// See `doc/book/src/reference/registries.md` for more details on
1102    /// alternative registries. See `alt_registry.rs` for the tests that use
1103    /// this.
1104    ///
1105    /// **Requires:** [`alt_init`]
1106    pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1107        self.alternative = alternative;
1108        self
1109    }
1110
1111    /// Adds a file to the package.
1112    pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1113        self.file_with_mode(name, DEFAULT_MODE, contents)
1114    }
1115
1116    /// Adds a file with a specific Unix mode.
1117    pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1118        self.files.push(PackageFile {
1119            path: path.to_string(),
1120            contents: EntryData::Regular(contents.into()),
1121            mode,
1122            extra: false,
1123        });
1124        self
1125    }
1126
1127    /// Adds a symlink to a path to the package.
1128    pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1129        self.files.push(PackageFile {
1130            path: dst.to_string(),
1131            contents: EntryData::Symlink(src.into()),
1132            mode: DEFAULT_MODE,
1133            extra: false,
1134        });
1135        self
1136    }
1137
1138    /// Adds an empty directory at the given path.
1139    pub fn directory(&mut self, path: &str) -> &mut Package {
1140        self.files.push(PackageFile {
1141            path: path.to_string(),
1142            contents: EntryData::Directory,
1143            mode: DEFAULT_MODE,
1144            extra: false,
1145        });
1146        self
1147    }
1148
1149    /// Adds an "extra" file that is not rooted within the package.
1150    ///
1151    /// Normal files are automatically placed within a directory named
1152    /// `$PACKAGE-$VERSION`. This allows you to override that behavior,
1153    /// typically for testing invalid behavior.
1154    pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1155        self.files.push(PackageFile {
1156            path: path.to_string(),
1157            contents: EntryData::Regular(contents.to_string()),
1158            mode: DEFAULT_MODE,
1159            extra: true,
1160        });
1161        self
1162    }
1163
1164    /// Adds a normal dependency. Example:
1165    /// ```toml
1166    /// [dependencies]
1167    /// foo = {version = "1.0"}
1168    /// ```
1169    pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1170        self.add_dep(&Dependency::new(name, vers))
1171    }
1172
1173    /// Adds a dependency with the given feature. Example:
1174    /// ```toml
1175    /// [dependencies]
1176    /// foo = {version = "1.0", "features": ["feat1", "feat2"]}
1177    /// ```
1178    pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1179        self.add_dep(Dependency::new(name, vers).enable_features(features))
1180    }
1181
1182    /// Adds a platform-specific dependency. Example:
1183    /// ```toml
1184    /// [target.'cfg(windows)'.dependencies]
1185    /// foo = {version = "1.0"}
1186    /// ```
1187    pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1188        self.add_dep(Dependency::new(name, vers).target(target))
1189    }
1190
1191    /// Adds a dependency to the alternative registry.
1192    pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1193        self.add_dep(Dependency::new(name, vers).registry("alternative"))
1194    }
1195
1196    /// Adds a dev-dependency. Example:
1197    /// ```toml
1198    /// [dev-dependencies]
1199    /// foo = {version = "1.0"}
1200    /// ```
1201    pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1202        self.add_dep(Dependency::new(name, vers).dev())
1203    }
1204
1205    /// Adds a build-dependency. Example:
1206    /// ```toml
1207    /// [build-dependencies]
1208    /// foo = {version = "1.0"}
1209    /// ```
1210    pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1211        self.add_dep(Dependency::new(name, vers).build())
1212    }
1213
1214    pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1215        self.deps.push(dep.clone());
1216        self
1217    }
1218
1219    /// Specifies whether or not the package is "yanked".
1220    pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1221        self.yanked = yanked;
1222        self
1223    }
1224
1225    /// Specifies `package.edition`
1226    pub fn edition(&mut self, edition: &str) -> &mut Package {
1227        self.edition = Some(edition.to_owned());
1228        self
1229    }
1230
1231    /// Specifies `package.resolver`
1232    pub fn resolver(&mut self, resolver: &str) -> &mut Package {
1233        self.resolver = Some(resolver.to_owned());
1234        self
1235    }
1236
1237    /// Specifies whether or not this is a proc macro.
1238    pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1239        self.proc_macro = proc_macro;
1240        self
1241    }
1242
1243    /// Adds an entry in the `[features]` section.
1244    pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1245        let deps = deps.iter().map(|s| s.to_string()).collect();
1246        self.features.insert(name.to_string(), deps);
1247        self
1248    }
1249
1250    /// Specify a minimal Rust version.
1251    pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1252        self.rust_version = Some(rust_version.into());
1253        self
1254    }
1255
1256    /// Causes the JSON line emitted in the index to be invalid, presumably
1257    /// causing Cargo to skip over this version.
1258    pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package {
1259        self.invalid_index_line = invalid;
1260        self
1261    }
1262
1263    /// Override the auto-generated index line
1264    ///
1265    /// This can give more control over error cases than [`Package::invalid_index_line`]
1266    pub fn index_line(&mut self, line: &str) -> &mut Package {
1267        self.index_line = Some(line.to_owned());
1268        self
1269    }
1270
1271    pub fn links(&mut self, links: &str) -> &mut Package {
1272        self.links = Some(links.to_string());
1273        self
1274    }
1275
1276    pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1277        self.cargo_features.push(feature.to_owned());
1278        self
1279    }
1280
1281    /// The publish time for the package in ISO8601 with UTC timezone (e.g. 2025-11-12T19:30:12Z)
1282    pub fn pubtime(&mut self, time: &str) -> &mut Package {
1283        self.pubtime = Some(time.to_owned());
1284        self
1285    }
1286
1287    /// Sets the index schema version for this package.
1288    ///
1289    /// See `cargo::sources::registry::IndexPackage` for more information.
1290    pub fn schema_version(&mut self, version: u32) -> &mut Package {
1291        self.v = Some(version);
1292        self
1293    }
1294
1295    /// Creates the package and place it in the registry.
1296    ///
1297    /// This does not actually use Cargo's publishing system, but instead
1298    /// manually creates the entry in the registry on the filesystem.
1299    ///
1300    /// Returns the checksum for the package.
1301    pub fn publish(&self) -> String {
1302        self.make_archive();
1303
1304        // Figure out what we're going to write into the index.
1305        let deps = self
1306            .deps
1307            .iter()
1308            .map(|dep| {
1309                // In the index, the `registry` is null if it is from the same registry.
1310                // In Cargo.toml, it is None if it is from crates.io.
1311                let registry_url = match (self.alternative, dep.registry.as_deref()) {
1312                    (false, None) => None,
1313                    (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1314                    (true, None) => {
1315                        Some("https://github.com/rust-lang/crates.io-index".to_string())
1316                    }
1317                    (true, Some("alternative")) => None,
1318                    _ => panic!("registry_dep currently only supports `alternative`"),
1319                };
1320                let artifact = if let Some(artifact) = &dep.artifact {
1321                    serde_json::json!([artifact])
1322                } else {
1323                    serde_json::json!(null)
1324                };
1325                serde_json::json!({
1326                    "name": dep.name,
1327                    "req": dep.vers,
1328                    "features": dep.features,
1329                    "default_features": dep.default_features,
1330                    "target": dep.target,
1331                    "artifact": artifact,
1332                    "bindep_target": dep.bindep_target,
1333                    "lib": dep.lib,
1334                    "optional": dep.optional,
1335                    "kind": dep.kind,
1336                    "registry": registry_url,
1337                    "package": dep.package,
1338                    "public": dep.public,
1339                })
1340            })
1341            .collect::<Vec<_>>();
1342        let cksum = {
1343            let c = t!(fs::read(&self.archive_dst()));
1344            cksum(&c)
1345        };
1346        let line = if let Some(line) = self.index_line.clone() {
1347            line
1348        } else {
1349            let name = if self.invalid_index_line {
1350                serde_json::json!(1)
1351            } else {
1352                serde_json::json!(self.name)
1353            };
1354            create_index_line(
1355                name,
1356                &self.vers,
1357                deps,
1358                &cksum,
1359                self.features.clone(),
1360                self.yanked,
1361                self.links.clone(),
1362                self.rust_version.as_deref(),
1363                self.pubtime.as_deref(),
1364                self.v,
1365            )
1366        };
1367
1368        let registry_path = if self.alternative {
1369            alt_registry_path()
1370        } else {
1371            registry_path()
1372        };
1373
1374        write_to_index(&registry_path, &self.name, line, self.local);
1375
1376        cksum
1377    }
1378
1379    fn make_archive(&self) {
1380        let dst = self.archive_dst();
1381        t!(fs::create_dir_all(dst.parent().unwrap()));
1382        let f = t!(File::create(&dst));
1383        let mut a = Builder::new(GzEncoder::new(f, Compression::none()));
1384        a.sparse(false);
1385
1386        if !self
1387            .files
1388            .iter()
1389            .any(|PackageFile { path, .. }| path == "Cargo.toml")
1390        {
1391            self.append_manifest(&mut a);
1392        }
1393        if self.files.is_empty() {
1394            self.append(
1395                &mut a,
1396                "src/lib.rs",
1397                DEFAULT_MODE,
1398                &EntryData::Regular("".into()),
1399            );
1400        } else {
1401            for PackageFile {
1402                path,
1403                contents,
1404                mode,
1405                extra,
1406            } in &self.files
1407            {
1408                if *extra {
1409                    self.append_raw(&mut a, path, *mode, contents);
1410                } else {
1411                    self.append(&mut a, path, *mode, contents);
1412                }
1413            }
1414        }
1415    }
1416
1417    fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1418        let mut manifest = String::new();
1419
1420        if !self.cargo_features.is_empty() {
1421            let mut features = String::new();
1422            serde::Serialize::serialize(
1423                &self.cargo_features,
1424                toml::ser::ValueSerializer::new(&mut features),
1425            )
1426            .unwrap();
1427            manifest.push_str(&format!("cargo-features = {}\n\n", features));
1428        }
1429
1430        manifest.push_str(&format!(
1431            r#"
1432            [package]
1433            name = "{}"
1434            version = "{}"
1435            authors = []
1436        "#,
1437            self.name, self.vers
1438        ));
1439
1440        if let Some(version) = &self.rust_version {
1441            manifest.push_str(&format!("rust-version = \"{}\"\n", version));
1442        }
1443
1444        if let Some(edition) = &self.edition {
1445            manifest.push_str(&format!("edition = \"{}\"\n", edition));
1446        }
1447
1448        if let Some(resolver) = &self.resolver {
1449            manifest.push_str(&format!("resolver = \"{}\"\n", resolver));
1450        }
1451
1452        if !self.features.is_empty() {
1453            let features: Vec<String> = self
1454                .features
1455                .iter()
1456                .map(|(feature, features)| {
1457                    if features.is_empty() {
1458                        format!("{} = []", feature)
1459                    } else {
1460                        format!(
1461                            "{} = [{}]",
1462                            feature,
1463                            features
1464                                .iter()
1465                                .map(|s| format!("\"{}\"", s))
1466                                .collect::<Vec<_>>()
1467                                .join(", ")
1468                        )
1469                    }
1470                })
1471                .collect();
1472
1473            manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
1474        }
1475
1476        for dep in self.deps.iter() {
1477            let target = match dep.target {
1478                None => String::new(),
1479                Some(ref s) => format!("target.'{}'.", s),
1480            };
1481            let kind = match &dep.kind[..] {
1482                "build" => "build-",
1483                "dev" => "dev-",
1484                _ => "",
1485            };
1486            manifest.push_str(&format!(
1487                r#"
1488                [{}{}dependencies.{}]
1489                version = "{}"
1490            "#,
1491                target, kind, dep.name, dep.vers
1492            ));
1493            if dep.optional {
1494                manifest.push_str("optional = true\n");
1495            }
1496            if let Some(artifact) = &dep.artifact {
1497                manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
1498            }
1499            if let Some(target) = &dep.bindep_target {
1500                manifest.push_str(&format!("target = \"{}\"\n", target));
1501            }
1502            if dep.lib {
1503                manifest.push_str("lib = true\n");
1504            }
1505            if let Some(registry) = &dep.registry {
1506                assert_eq!(registry, "alternative");
1507                manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1508            }
1509            if !dep.default_features {
1510                manifest.push_str("default-features = false\n");
1511            }
1512            if !dep.features.is_empty() {
1513                let mut features = String::new();
1514                serde::Serialize::serialize(
1515                    &dep.features,
1516                    toml::ser::ValueSerializer::new(&mut features),
1517                )
1518                .unwrap();
1519                manifest.push_str(&format!("features = {}\n", features));
1520            }
1521            if let Some(package) = &dep.package {
1522                manifest.push_str(&format!("package = \"{}\"\n", package));
1523            }
1524        }
1525        if self.proc_macro {
1526            manifest.push_str("[lib]\nproc-macro = true\n");
1527        }
1528
1529        self.append(
1530            ar,
1531            "Cargo.toml",
1532            DEFAULT_MODE,
1533            &EntryData::Regular(manifest.into()),
1534        );
1535    }
1536
1537    fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1538        self.append_raw(
1539            ar,
1540            &format!("{}-{}/{}", self.name, self.vers, file),
1541            mode,
1542            contents,
1543        );
1544    }
1545
1546    fn append_raw<W: Write>(
1547        &self,
1548        ar: &mut Builder<W>,
1549        path: &str,
1550        mode: u32,
1551        contents: &EntryData,
1552    ) {
1553        // Unfortunately we cannot use GNU headers with dynamic extensions for
1554        // long paths because that would cause package checksums to change
1555        // based on whether or not the tests are running in a long directory
1556        // name.
1557        let mut header = Header::new_ustar();
1558        let contents = match contents {
1559            EntryData::Regular(contents) => contents.as_str(),
1560            EntryData::Symlink(src) => {
1561                header.set_entry_type(tar::EntryType::Symlink);
1562                t!(header.set_link_name(src));
1563                "" // Symlink has no contents.
1564            }
1565            EntryData::Directory => {
1566                header.set_entry_type(tar::EntryType::Directory);
1567                ""
1568            }
1569        };
1570        header.set_size(contents.len() as u64);
1571        t!(header.set_path(path));
1572        header.set_mode(mode);
1573        header.set_cksum();
1574        t!(ar.append(&header, contents.as_bytes()));
1575    }
1576
1577    /// Returns the path to the compressed package file.
1578    pub fn archive_dst(&self) -> PathBuf {
1579        if self.local {
1580            let path = if self.alternative {
1581                alt_registry_path()
1582            } else {
1583                registry_path()
1584            };
1585            path.join(format!("{}-{}.crate", self.name, self.vers))
1586        } else if self.alternative {
1587            alt_dl_path()
1588                .join(&self.name)
1589                .join(&self.vers)
1590                .join("download")
1591        } else {
1592            dl_path().join(&self.name).join(&self.vers).join("download")
1593        }
1594    }
1595}
1596
1597/// Generate a checksum
1598pub fn cksum(s: &[u8]) -> String {
1599    Sha256::new().update(s).finish_hex()
1600}
1601
1602impl Dependency {
1603    pub fn new(name: &str, vers: &str) -> Dependency {
1604        Dependency {
1605            name: name.to_string(),
1606            vers: vers.to_string(),
1607            kind: "normal".to_string(),
1608            artifact: None,
1609            bindep_target: None,
1610            lib: false,
1611            target: None,
1612            features: Vec::new(),
1613            package: None,
1614            optional: false,
1615            registry: None,
1616            default_features: true,
1617            public: false,
1618        }
1619    }
1620
1621    /// Changes this to `[build-dependencies]`.
1622    pub fn build(&mut self) -> &mut Self {
1623        self.kind = "build".to_string();
1624        self
1625    }
1626
1627    /// Changes this to `[dev-dependencies]`.
1628    pub fn dev(&mut self) -> &mut Self {
1629        self.kind = "dev".to_string();
1630        self
1631    }
1632
1633    /// Changes this to `[target.$target.dependencies]`.
1634    pub fn target(&mut self, target: &str) -> &mut Self {
1635        self.target = Some(target.to_string());
1636        self
1637    }
1638
1639    /// Change the artifact to be of the given kind, like "bin", or "staticlib",
1640    /// along with a specific target tuple if provided.
1641    pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
1642        self.artifact = Some(kind.to_string());
1643        self.bindep_target = target;
1644        self
1645    }
1646
1647    /// Adds `registry = $registry` to this dependency.
1648    pub fn registry(&mut self, registry: &str) -> &mut Self {
1649        self.registry = Some(registry.to_string());
1650        self
1651    }
1652
1653    /// Adds `features = [ ... ]` to this dependency.
1654    pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1655        self.features.extend(features.iter().map(|s| s.to_string()));
1656        self
1657    }
1658
1659    /// Adds `package = ...` to this dependency.
1660    pub fn package(&mut self, pkg: &str) -> &mut Self {
1661        self.package = Some(pkg.to_string());
1662        self
1663    }
1664
1665    /// Changes this to an optional dependency.
1666    pub fn optional(&mut self, optional: bool) -> &mut Self {
1667        self.optional = optional;
1668        self
1669    }
1670
1671    /// Changes this to an public dependency.
1672    pub fn public(&mut self, public: bool) -> &mut Self {
1673        self.public = public;
1674        self
1675    }
1676
1677    /// Adds `default-features = false` if the argument is `false`.
1678    pub fn default_features(&mut self, default_features: bool) -> &mut Self {
1679        self.default_features = default_features;
1680        self
1681    }
1682}