├── .gitignore ├── .rustfmt.toml ├── .github ├── dependabot.yml ├── scripts │ ├── check-release.sh │ └── is-latest-release.sh └── workflows │ └── release.yml ├── src ├── byte_count.rs ├── mime.rs ├── nd_json.rs ├── csv.rs └── main.rs ├── LICENSE ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | use_small_heuristics = "max" 4 | imports_granularity = "Module" 5 | group_imports = "StdExternalCrate" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions only 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "monthly" 9 | labels: 10 | - "skip changelog" 11 | - "dependencies" 12 | rebase-strategy: disabled 13 | -------------------------------------------------------------------------------- /src/byte_count.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | pub struct ByteCount(usize); 4 | 5 | impl ByteCount { 6 | pub fn new() -> Self { 7 | ByteCount(0) 8 | } 9 | 10 | pub fn count(&self) -> usize { 11 | self.0 12 | } 13 | } 14 | 15 | impl Write for ByteCount { 16 | fn write(&mut self, buf: &[u8]) -> io::Result { 17 | self.0 += buf.len(); 18 | Ok(buf.len()) 19 | } 20 | fn flush(&mut self) -> io::Result<()> { 21 | Ok(()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/scripts/check-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # check_tag $current_tag $file_tag $file_name 4 | function check_tag { 5 | if [[ "$1" != "$2" ]]; then 6 | echo "Error: the current tag does not match the version in $3: found $2 - expected $1" 7 | ret=1 8 | fi 9 | } 10 | 11 | ret=0 12 | current_tag=${GITHUB_REF#'refs/tags/v'} 13 | 14 | cargo_file='Cargo.toml' 15 | file_tag="$(grep '^version = ' $toml_file | cut -d '=' -f 2 | tr -d '"' | tr -d ' ')" 16 | check_tag $current_tag $file_tag $cargo_file 17 | 18 | if [[ "$ret" -eq 0 ]] ; then 19 | echo 'OK' 20 | fi 21 | exit $ret -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Meili SAS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meilisearch-importer" 3 | description = "A tool to import massive datasets into Meilisearch by sending them in batches" 4 | version = "0.2.2" 5 | repository = "https://github.com/meilisearch/meilisearch-importer" 6 | license = "MIT" 7 | edition = "2021" 8 | 9 | [dependencies] 10 | anyhow = "1.0.81" 11 | byte-unit = { version = "5.1.4", features = ["byte", "serde"] } 12 | clap = { version = "4.5.3", features = ["derive"] } 13 | csv = "1.3.0" 14 | exponential-backoff = "1.2.0" 15 | flate2 = "1.0" 16 | indicatif = "0.17.8" 17 | serde_json = { version = "1.0.114", features = ["preserve_order"] } 18 | ureq = "2.9.6" 19 | 20 | # The profile that 'cargo dist' will build with 21 | [profile.dist] 22 | inherits = "release" 23 | lto = "thin" 24 | 25 | # Config for 'cargo dist' 26 | [workspace.metadata.dist] 27 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 28 | cargo-dist-version = "0.11.1" 29 | # The installers to generate for each app 30 | installers = [] 31 | # Target platforms to build apps for (Rust target-triple syntax) 32 | targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] 33 | # CI backends to support 34 | ci = ["github"] 35 | # Publish jobs to run in CI 36 | pr-run-mode = "plan" 37 | -------------------------------------------------------------------------------- /src/mime.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::str::FromStr; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum Mime { 6 | Json, 7 | NdJson, 8 | Csv, 9 | } 10 | 11 | impl Mime { 12 | pub fn from_path(path: &Path) -> Option { 13 | match path.extension().and_then(|ext| ext.to_str()) { 14 | Some("json") => Some(Mime::Json), 15 | Some("ndjson" | "jsonl") => Some(Mime::NdJson), 16 | Some("csv") => Some(Mime::Csv), 17 | _ => None, 18 | } 19 | } 20 | 21 | pub fn as_str(&self) -> &'static str { 22 | match self { 23 | Mime::Json => "application/json", 24 | Mime::NdJson => "application/x-ndjson", 25 | Mime::Csv => "text/csv", 26 | } 27 | } 28 | } 29 | 30 | impl FromStr for Mime { 31 | type Err = anyhow::Error; 32 | 33 | fn from_str(s: &str) -> Result { 34 | match s.to_lowercase().as_str() { 35 | "json" => Ok(Mime::Json), 36 | "ndjson" | "jsonl" => Ok(Mime::NdJson), 37 | "csv" => Ok(Mime::Csv), 38 | otherwise => anyhow::bail!( 39 | "unkown {otherwise} file format. Possible values are json, ndjson, jsonl, and csv." 40 | ), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meilisearch Importer 2 | 3 | The most efficient CLI tool to import massive CSVs, NSJSON or JSON (array of objects) into Meilisearch. 4 | 5 | This tool has been tested with multiple datasets from hundreds of thousand documents to some with more than forty millions documents. The progress bar is very handy in this case. 6 | 7 | ## Features 8 | 9 | - Uploads millions of documents to Meilisearch. 10 | - Automatically retries on error. 11 | - Shows the upload progress along with the estimated time of arrival (ETA). 12 | - [Works on the Cloud](https://www.meilisearch.com/pricing) and on self-hosted instances. 13 | 14 | ## Installation 15 | 16 | You can download the latest version of this tool [on the release page](https://github.com/meilisearch/meilisearch-importer/releases). 17 | 18 | ## Example Usage 19 | 20 | ### Send Documents to the Cloud 21 | 22 | It's straightforward to [create a project on the Cloud](https://www.meilisearch.com/pricing) and send your documents into it. 23 | 24 | If you cannot send your dataset directly from the website by drag-and-dropping it, this tool is perfect for you. You can send them by running the following command: 25 | 26 | ```bash 27 | meilisearch-importer \ 28 | --url 'https://ms-************.sfo.meilisearch.io' 29 | --index crunchbase \ 30 | --primary-key uuid \ 31 | --api-key 'D2jkS***************' \ 32 | --files ./dataset/organizations.csv 33 | ``` 34 | 35 | ### Send Documents to a Local Instance 36 | 37 | This tool is also useful when you want to test Meilisearch locally. The only mandatory parameters to define are the URL, the index name and your dataset. 38 | 39 | However, you can also increase the batch size to make meilisearch index faster. 40 | 41 | ```bash 42 | meilisearch-importer \ 43 | --url 'http://localhost:7700' 44 | --index movies \ 45 | --files movies.json \ 46 | --batch-size 100MB 47 | ``` 48 | -------------------------------------------------------------------------------- /src/nd_json.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, Read}; 3 | use std::path::{Path, PathBuf}; 4 | use std::{io, mem}; 5 | 6 | use serde_json::de::IoRead; 7 | use serde_json::{to_writer, Deserializer, Map, StreamDeserializer, Value}; 8 | 9 | use crate::byte_count::ByteCount; 10 | 11 | pub struct NdJsonChunker { 12 | #[allow(clippy::type_complexity)] 13 | pub reader: StreamDeserializer<'static, IoRead>>, Map>, 14 | pub buffer: Vec, 15 | pub size: usize, 16 | } 17 | 18 | impl NdJsonChunker { 19 | pub fn new(path: PathBuf, size: usize) -> Self { 20 | let reader = if path == Path::new("-") { 21 | Box::new(io::stdin()) as Box 22 | } else { 23 | Box::new(File::open(path).unwrap()) 24 | }; 25 | let reader = BufReader::new(reader); 26 | Self { reader: Deserializer::from_reader(reader).into_iter(), buffer: Vec::new(), size } 27 | } 28 | } 29 | 30 | impl Iterator for NdJsonChunker { 31 | type Item = Vec; 32 | 33 | fn next(&mut self) -> Option { 34 | for result in self.reader.by_ref() { 35 | let object = result.unwrap(); 36 | 37 | // Evaluate the size it will take if we serialize it in the buffer 38 | let mut counter = ByteCount::new(); 39 | to_writer(&mut counter, &object).unwrap(); 40 | 41 | if self.buffer.len() + counter.count() >= self.size { 42 | let buffer = mem::take(&mut self.buffer); 43 | // Insert the record but after we sent the buffer 44 | to_writer(&mut self.buffer, &object).unwrap(); 45 | return Some(buffer); 46 | } else { 47 | // Insert the record 48 | to_writer(&mut self.buffer, &object).unwrap(); 49 | } 50 | } 51 | if self.buffer.is_empty() { 52 | None 53 | } else { 54 | Some(mem::take(&mut self.buffer)) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/csv.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Read}; 3 | use std::mem; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use csv::{ByteRecord, WriterBuilder}; 7 | 8 | pub struct CsvChunker { 9 | pub(crate) reader: csv::Reader>, 10 | pub(crate) headers: ByteRecord, 11 | pub(crate) writer: csv::Writer>, 12 | pub(crate) record_count: usize, 13 | pub(crate) record: ByteRecord, 14 | pub(crate) size: usize, 15 | pub(crate) delimiter: u8, 16 | } 17 | 18 | impl CsvChunker { 19 | pub fn new(path: PathBuf, size: usize, delimiter: u8) -> Self { 20 | let reader = if path == Path::new("-") { 21 | Box::new(io::stdin()) as Box 22 | } else { 23 | Box::new(File::open(path).unwrap()) 24 | }; 25 | let mut reader = csv::Reader::from_reader(reader); 26 | let mut writer = WriterBuilder::new().delimiter(delimiter).from_writer(Vec::new()); 27 | let headers = reader.byte_headers().unwrap().clone(); 28 | writer.write_byte_record(&headers).unwrap(); 29 | Self { 30 | reader, 31 | headers, 32 | writer, 33 | record_count: 0, 34 | record: ByteRecord::new(), 35 | size, 36 | delimiter, 37 | } 38 | } 39 | } 40 | 41 | impl Iterator for CsvChunker { 42 | type Item = Vec; 43 | 44 | fn next(&mut self) -> Option { 45 | while self.reader.read_byte_record(&mut self.record).unwrap() { 46 | self.writer.flush().unwrap(); 47 | if self.writer.get_ref().len() + self.record.len() >= self.size { 48 | let mut writer = 49 | WriterBuilder::new().delimiter(self.delimiter).from_writer(Vec::new()); 50 | writer.write_byte_record(&self.headers).unwrap(); 51 | self.record_count = 0; 52 | let writer = mem::replace(&mut self.writer, writer); 53 | 54 | // Insert the header and out of bound record 55 | self.writer.write_byte_record(&self.headers).unwrap(); 56 | self.writer.write_byte_record(&self.record).unwrap(); 57 | self.record_count += 1; 58 | 59 | return Some(writer.into_inner().unwrap()); 60 | } else { 61 | // Insert only the record 62 | self.writer.write_byte_record(&self.record).unwrap(); 63 | self.record_count += 1; 64 | } 65 | } 66 | if self.record_count == 0 { 67 | None 68 | } else { 69 | let mut writer = WriterBuilder::new().delimiter(self.delimiter).from_writer(Vec::new()); 70 | writer.write_byte_record(&self.headers).unwrap(); 71 | self.record_count = 0; 72 | // We make the buffer empty by doing that and next time we will 73 | // come back to this _if else_ condition to then return None. 74 | let writer = mem::replace(&mut self.writer, writer); 75 | Some(writer.into_inner().unwrap()) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.github/scripts/is-latest-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Was used in our CIs to publish the latest docker image. Not used anymore, will be used again when v1 and v2 will be out and we will want to maintain multiple stable versions. 4 | # Returns "true" or "false" (as a string) to be used in the `if` in GHA 5 | 6 | # Checks if the current tag should be the latest (in terms of semver and not of release date). 7 | # Ex: previous tag -> v2.1.1 8 | # new tag -> v1.20.3 9 | # The new tag (v1.20.3) should NOT be the latest 10 | # So it returns "false", the `latest` tag should not be updated for the release v1.20.3 and still need to correspond to v2.1.1 11 | 12 | # GLOBAL 13 | GREP_SEMVER_REGEXP='v\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)$' # i.e. v[number].[number].[number] 14 | 15 | # FUNCTIONS 16 | 17 | # semverParseInto and semverLT from https://github.com/cloudflare/semver_bash/blob/master/semver.sh 18 | 19 | # usage: semverParseInto version major minor patch special 20 | # version: the string version 21 | # major, minor, patch, special: will be assigned by the function 22 | semverParseInto() { 23 | local RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)' 24 | #MAJOR 25 | eval $2=`echo $1 | sed -e "s#$RE#\1#"` 26 | #MINOR 27 | eval $3=`echo $1 | sed -e "s#$RE#\2#"` 28 | #MINOR 29 | eval $4=`echo $1 | sed -e "s#$RE#\3#"` 30 | #SPECIAL 31 | eval $5=`echo $1 | sed -e "s#$RE#\4#"` 32 | } 33 | 34 | # usage: semverLT version1 version2 35 | semverLT() { 36 | local MAJOR_A=0 37 | local MINOR_A=0 38 | local PATCH_A=0 39 | local SPECIAL_A=0 40 | 41 | local MAJOR_B=0 42 | local MINOR_B=0 43 | local PATCH_B=0 44 | local SPECIAL_B=0 45 | 46 | semverParseInto $1 MAJOR_A MINOR_A PATCH_A SPECIAL_A 47 | semverParseInto $2 MAJOR_B MINOR_B PATCH_B SPECIAL_B 48 | 49 | if [ $MAJOR_A -lt $MAJOR_B ]; then 50 | return 0 51 | fi 52 | if [ $MAJOR_A -le $MAJOR_B ] && [ $MINOR_A -lt $MINOR_B ]; then 53 | return 0 54 | fi 55 | if [ $MAJOR_A -le $MAJOR_B ] && [ $MINOR_A -le $MINOR_B ] && [ $PATCH_A -lt $PATCH_B ]; then 56 | return 0 57 | fi 58 | if [ "_$SPECIAL_A" == "_" ] && [ "_$SPECIAL_B" == "_" ] ; then 59 | return 1 60 | fi 61 | if [ "_$SPECIAL_A" == "_" ] && [ "_$SPECIAL_B" != "_" ] ; then 62 | return 1 63 | fi 64 | if [ "_$SPECIAL_A" != "_" ] && [ "_$SPECIAL_B" == "_" ] ; then 65 | return 0 66 | fi 67 | if [ "_$SPECIAL_A" < "_$SPECIAL_B" ]; then 68 | return 0 69 | fi 70 | 71 | return 1 72 | } 73 | 74 | # Returns the tag of the latest stable release (in terms of semver and not of release date) 75 | get_latest() { 76 | temp_file='temp_file' # temp_file needed because the grep would start before the download is over 77 | curl -s 'https://api.github.com/repos/meilisearch/meilisearch/releases' > "$temp_file" 78 | releases=$(cat "$temp_file" | \ 79 | grep -E "tag_name|draft|prerelease" \ 80 | | tr -d ',"' | cut -d ':' -f2 | tr -d ' ') 81 | # Returns a list of [tag_name draft_boolean prerelease_boolean ...] 82 | # Ex: v0.10.1 false false v0.9.1-rc.1 false true v0.9.0 false false... 83 | 84 | i=0 85 | latest="" 86 | current_tag="" 87 | for release_info in $releases; do 88 | if [ $i -eq 0 ]; then # Checking tag_name 89 | if echo "$release_info" | grep -q "$GREP_SEMVER_REGEXP"; then # If it's not an alpha or beta release 90 | current_tag=$release_info 91 | else 92 | current_tag="" 93 | fi 94 | i=1 95 | elif [ $i -eq 1 ]; then # Checking draft boolean 96 | if [ "$release_info" = "true" ]; then 97 | current_tag="" 98 | fi 99 | i=2 100 | elif [ $i -eq 2 ]; then # Checking prerelease boolean 101 | if [ "$release_info" = "true" ]; then 102 | current_tag="" 103 | fi 104 | i=0 105 | if [ "$current_tag" != "" ]; then # If the current_tag is valid 106 | if [ "$latest" = "" ]; then # If there is no latest yet 107 | latest="$current_tag" 108 | else 109 | semverLT $current_tag $latest # Comparing latest and the current tag 110 | if [ $? -eq 1 ]; then 111 | latest="$current_tag" 112 | fi 113 | fi 114 | fi 115 | fi 116 | done 117 | 118 | rm -f "$temp_file" 119 | echo $latest 120 | } 121 | 122 | # MAIN 123 | current_tag="$(echo $GITHUB_REF | tr -d 'refs/tags/')" 124 | latest="$(get_latest)" 125 | 126 | if [ "$current_tag" != "$latest" ]; then 127 | # The current release tag is not the latest 128 | echo "false" 129 | else 130 | # The current release tag is the latest 131 | echo "true" 132 | fi -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::path::{Path, PathBuf}; 3 | use std::time::Duration; 4 | use std::{fs, thread}; 5 | 6 | use anyhow::Context; 7 | use byte_unit::Byte; 8 | use clap::{Parser, ValueEnum}; 9 | use exponential_backoff::Backoff; 10 | use flate2::write::GzEncoder; 11 | use flate2::Compression; 12 | use indicatif::ProgressBar; 13 | use mime::Mime; 14 | use ureq::{Agent, AgentBuilder}; 15 | use serde_json; 16 | 17 | mod byte_count; 18 | mod csv; 19 | mod mime; 20 | mod nd_json; 21 | 22 | /// A tool to import massive datasets into Meilisearch by sending them in batches. 23 | #[derive(Debug, Parser, Clone)] 24 | #[command(name = "meilisearch-importer")] 25 | struct Opt { 26 | /// The URL of your instance. You can find it on the main project page on the Cloud. 27 | /// It looks like the following: 28 | /// 29 | /// https://ms-************.sfo.meilisearch.io 30 | #[structopt(long)] 31 | url: String, 32 | 33 | /// The index name you want to send your documents in. 34 | #[structopt(long)] 35 | index: String, 36 | 37 | /// The name of the field that must be used by Meilisearch to uniquely identify your documents. 38 | /// If not specified here, Meilisearch will try it's best to guess it. 39 | #[structopt(long)] 40 | primary_key: Option, 41 | 42 | /// The API key to access Meilisearch. This API key must have the `documents.add` right. 43 | /// The Master Key and the Default Admin API Key can be used to send documents. 44 | #[structopt(long)] 45 | api_key: Option, 46 | 47 | /// The delimiter to use for the CSV files. 48 | #[structopt(long, default_value_t = b',')] 49 | csv_delimiter: u8, 50 | 51 | /// A list of file paths that are streamed and sent to Meilisearch in batches, 52 | /// where content can come from stdin using the special minus (-) path. 53 | #[structopt(long, num_args(1..))] 54 | files: Vec, 55 | 56 | /// The file format to use. Overrides auto-detection, useful for stdin input (-). 57 | #[structopt(long)] 58 | format: Option, 59 | 60 | /// The size of the batches sent to Meilisearch. 61 | #[structopt(long, default_value = "20 MiB")] 62 | batch_size: Byte, 63 | 64 | /// The number of batches to skip. Useful when the upload stopped for some reason. 65 | #[structopt(long)] 66 | skip_batches: Option, 67 | 68 | /// The operation to perform when uploading a document. 69 | #[arg( 70 | long, 71 | value_name = "OPERATION", 72 | num_args = 0..=1, 73 | default_value_t = DocumentOperation::AddOrReplace, 74 | value_enum 75 | )] 76 | upload_operation: DocumentOperation, 77 | } 78 | 79 | #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] 80 | enum DocumentOperation { 81 | AddOrReplace, 82 | AddOrUpdate, 83 | } 84 | 85 | fn send_data( 86 | opt: &Opt, 87 | agent: &Agent, 88 | upload_operation: DocumentOperation, 89 | pb: &ProgressBar, 90 | mime: &Mime, 91 | data: &[u8], 92 | ) -> anyhow::Result<()> { 93 | let api_key = opt.api_key.clone(); 94 | let mut url = format!("{}/indexes/{}/documents", opt.url, opt.index); 95 | if let Some(primary_key) = &opt.primary_key { 96 | url = format!("{}?primaryKey={}", url, primary_key); 97 | } 98 | 99 | let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); 100 | encoder.write_all(data)?; 101 | let data = encoder.finish()?; 102 | 103 | let retries = 20; 104 | let min = Duration::from_millis(100); // 10ms 105 | let max = Duration::from_secs(60 * 60); // 1h 106 | let backoff = Backoff::new(retries, min, max); 107 | 108 | for (attempt, duration) in backoff.into_iter().enumerate() { 109 | let mut request = match upload_operation { 110 | DocumentOperation::AddOrReplace => agent.post(&url), 111 | DocumentOperation::AddOrUpdate => agent.put(&url), 112 | }; 113 | request = request.set("Content-Type", mime.as_str()); 114 | request = request.set("Content-Encoding", "gzip"); 115 | request = request.set("X-Meilisearch-Client", "Meilisearch Importer"); 116 | 117 | if let Some(api_key) = &api_key { 118 | request = request.set("Authorization", &format!("Bearer {}", api_key)); 119 | } 120 | 121 | match request.send_bytes(&data) { 122 | Ok(response) if matches!(response.status(), 200..=299) => { 123 | let resp_body = response.into_string()?; 124 | let task_uid: Option = serde_json::from_str::(&resp_body) 125 | .ok() 126 | .and_then(|v| v["taskUid"].as_u64()); 127 | if let Some(task_uid) = task_uid { 128 | let task_url = format!("{}/tasks/{}", opt.url, task_uid); 129 | loop { 130 | let mut req = agent.get(&task_url); 131 | if let Some(api_key) = &api_key { 132 | req = req.set("Authorization", &format!("Bearer {}", api_key)); 133 | } 134 | match req.call() { 135 | Ok(task_response) => { 136 | let task_json: serde_json::Value = serde_json::from_str(&task_response.into_string()?).unwrap_or_default(); 137 | let status = task_json["status"].as_str().unwrap_or(""); 138 | if status == "succeeded" { 139 | if let Some(failed) = task_json["details"]["failedDocuments"].as_u64() { 140 | if failed > 0 { 141 | pb.println(format!("⚠️ 批量导入有 {} 条失败,建议降级单条重试或导出失败文档!", failed)); 142 | } 143 | } 144 | break; 145 | } else if status == "failed" { 146 | pb.println(format!("❌ 批量导入任务失败: {:?}", task_json)); 147 | break; 148 | } 149 | } 150 | Err(e) => { 151 | pb.println(format!("查询任务状态失败: {},重试...", e)); 152 | } 153 | } 154 | std::thread::sleep(std::time::Duration::from_millis(500)); 155 | } 156 | } else { 157 | pb.println("⚠️ 未能解析批量导入任务 taskUid,无法确认导入结果!"); 158 | } 159 | return Ok(()); 160 | } 161 | Ok(response) => { 162 | let e = response.into_string()?; 163 | pb.println(format!("Attempt #{}: {}", attempt, e)); 164 | thread::sleep(duration); 165 | } 166 | Err(e) => { 167 | pb.println(format!("Attempt #{}: {}", attempt, e)); 168 | thread::sleep(duration); 169 | } 170 | } 171 | } 172 | 173 | if let Ok(text) = std::str::from_utf8(&data) { 174 | let is_single_json = matches!(mime, crate::mime::Mime::Json) && text.trim_start().starts_with('{'); 175 | let is_single_ndjson = matches!(mime, crate::mime::Mime::NdJson) && text.lines().count() == 1; 176 | if is_single_json || is_single_ndjson { 177 | pb.println("Batch failed, trying single-document retry..."); 178 | let single_doc = if is_single_json { 179 | text.as_bytes() 180 | } else { 181 | text.lines().next().unwrap().as_bytes() 182 | }; 183 | let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); 184 | encoder.write_all(single_doc)?; 185 | let single_data = encoder.finish()?; 186 | let mut request = match upload_operation { 187 | DocumentOperation::AddOrReplace => agent.post(&url), 188 | DocumentOperation::AddOrUpdate => agent.put(&url), 189 | }; 190 | request = request.set("Content-Type", mime.as_str()); 191 | request = request.set("Content-Encoding", "gzip"); 192 | request = request.set("X-Meilisearch-Client", "Meilisearch Importer"); 193 | if let Some(api_key) = &api_key { 194 | request = request.set("Authorization", &format!("Bearer {}", api_key)); 195 | } 196 | match request.send_bytes(&single_data) { 197 | Ok(response) if matches!(response.status(), 200..=299) => { 198 | pb.println("Single-document retry succeeded!"); 199 | return Ok(()); 200 | } 201 | Ok(response) => { 202 | let e = response.into_string()?; 203 | pb.println(format!("Single-doc retry failed: {}", e)); 204 | } 205 | Err(e) => { 206 | pb.println(format!("Single-doc retry error: {}", e)); 207 | } 208 | } 209 | } 210 | } 211 | 212 | anyhow::bail!("Too many errors. Stopping the retries.") 213 | } 214 | 215 | fn main() -> anyhow::Result<()> { 216 | let opt = Opt::parse(); 217 | let agent = AgentBuilder::new().timeout(Duration::from_secs(30)).build(); 218 | let files = opt.files.clone(); 219 | 220 | // for each files present in the argument 221 | for path in files { 222 | // check if the file exists 223 | if path != Path::new("-") && !path.exists() { 224 | anyhow::bail!("The file {:?} does not exist", path); 225 | } 226 | 227 | let mime = match opt.format { 228 | Some(mime) => mime, 229 | None => Mime::from_path(&path).context("Could not find the mime type")?, 230 | }; 231 | 232 | let file_size = if path == Path::new("-") { 0 } else { fs::metadata(&path)?.len() }; 233 | let size = opt.batch_size.as_u64() as usize; 234 | let nb_chunks = file_size / size as u64; 235 | let pb = ProgressBar::new(nb_chunks); 236 | pb.inc(0); 237 | 238 | match mime { 239 | Mime::Json => { 240 | if opt.skip_batches.zip(pb.length()).map_or(true, |(s, l)| s > l) { 241 | let data = fs::read_to_string(path)?; 242 | send_data(&opt, &agent, opt.upload_operation, &pb, &mime, data.as_bytes())?; 243 | } 244 | pb.inc(1); 245 | } 246 | Mime::NdJson => { 247 | for chunk in nd_json::NdJsonChunker::new(path, size) { 248 | if opt.skip_batches.zip(pb.length()).map_or(true, |(s, l)| s > l) { 249 | send_data(&opt, &agent, opt.upload_operation, &pb, &mime, &chunk)?; 250 | } 251 | pb.inc(1); 252 | } 253 | } 254 | Mime::Csv => { 255 | for chunk in csv::CsvChunker::new(path, size, opt.csv_delimiter) { 256 | if opt.skip_batches.zip(pb.length()).map_or(true, |(s, l)| s > l) { 257 | send_data(&opt, &agent, opt.upload_operation, &pb, &mime, &chunk)?; 258 | } 259 | pb.inc(1); 260 | } 261 | } 262 | } 263 | } 264 | 265 | Ok(()) 266 | } 267 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023, axodotdev 2 | # SPDX-License-Identifier: MIT or Apache-2.0 3 | # 4 | # CI that: 5 | # 6 | # * checks for a Git Tag that looks like a release 7 | # * builds artifacts with cargo-dist (archives, installers, hashes) 8 | # * uploads those artifacts to temporary workflow zip 9 | # * on success, uploads the artifacts to a Github Release 10 | # 11 | # Note that the Github Release will be created with a generated 12 | # title/body based on your changelogs. 13 | 14 | name: Release 15 | 16 | permissions: 17 | contents: write 18 | 19 | # This task will run whenever you push a git tag that looks like a version 20 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 21 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 22 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 23 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 24 | # 25 | # If PACKAGE_NAME is specified, then the announcement will be for that 26 | # package (erroring out if it doesn't have the given version or isn't cargo-dist-able). 27 | # 28 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 29 | # (cargo-dist-able) packages in the workspace with that version (this mode is 30 | # intended for workspaces with only one dist-able package, or with all dist-able 31 | # packages versioned/released in lockstep). 32 | # 33 | # If you push multiple tags at once, separate instances of this workflow will 34 | # spin up, creating an independent announcement for each one. However Github 35 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 36 | # mistake. 37 | # 38 | # If there's a prerelease-style suffix to the version, then the release(s) 39 | # will be marked as a prerelease. 40 | on: 41 | push: 42 | tags: 43 | - '**[0-9]+.[0-9]+.[0-9]+*' 44 | pull_request: 45 | 46 | jobs: 47 | # Run 'cargo dist plan' (or host) to determine what tasks we need to do 48 | plan: 49 | runs-on: ubuntu-latest 50 | outputs: 51 | val: ${{ steps.plan.outputs.manifest }} 52 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 53 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 54 | publishing: ${{ !github.event.pull_request }} 55 | env: 56 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | submodules: recursive 61 | - name: Install cargo-dist 62 | # we specify bash to get pipefail; it guards against the `curl` command 63 | # failing. otherwise `sh` won't catch that `curl` returned non-0 64 | shell: bash 65 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 66 | # sure would be cool if github gave us proper conditionals... 67 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 68 | # functionality based on whether this is a pull_request, and whether it's from a fork. 69 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 70 | # but also really annoying to build CI around when it needs secrets to work right.) 71 | - id: plan 72 | run: | 73 | cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 74 | echo "cargo dist ran successfully" 75 | cat plan-dist-manifest.json 76 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 77 | - name: "Upload dist-manifest.json" 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: artifacts-plan-dist-manifest 81 | path: plan-dist-manifest.json 82 | 83 | # Build and packages all the platform-specific things 84 | build-local-artifacts: 85 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 86 | # Let the initial task tell us to not run (currently very blunt) 87 | needs: 88 | - plan 89 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 90 | strategy: 91 | fail-fast: false 92 | # Target platforms/runners are computed by cargo-dist in create-release. 93 | # Each member of the matrix has the following arguments: 94 | # 95 | # - runner: the github runner 96 | # - dist-args: cli flags to pass to cargo dist 97 | # - install-dist: expression to run to install cargo-dist on the runner 98 | # 99 | # Typically there will be: 100 | # - 1 "global" task that builds universal installers 101 | # - N "local" tasks that build each platform's binaries and platform-specific installers 102 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 103 | runs-on: ${{ matrix.runner }} 104 | env: 105 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 107 | steps: 108 | - uses: actions/checkout@v4 109 | with: 110 | submodules: recursive 111 | - uses: swatinem/rust-cache@v2 112 | - name: Install cargo-dist 113 | run: ${{ matrix.install_dist }} 114 | # Get the dist-manifest 115 | - name: Fetch local artifacts 116 | uses: actions/download-artifact@v4 117 | with: 118 | pattern: artifacts-* 119 | path: target/distrib/ 120 | merge-multiple: true 121 | - name: Install dependencies 122 | run: | 123 | ${{ matrix.packages_install }} 124 | - name: Build artifacts 125 | run: | 126 | # Actually do builds and make zips and whatnot 127 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 128 | echo "cargo dist ran successfully" 129 | - id: cargo-dist 130 | name: Post-build 131 | # We force bash here just because github makes it really hard to get values up 132 | # to "real" actions without writing to env-vars, and writing to env-vars has 133 | # inconsistent syntax between shell and powershell. 134 | shell: bash 135 | run: | 136 | # Parse out what we just built and upload it to scratch storage 137 | echo "paths<> "$GITHUB_OUTPUT" 138 | jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" 139 | echo "EOF" >> "$GITHUB_OUTPUT" 140 | 141 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 142 | - name: "Upload artifacts" 143 | uses: actions/upload-artifact@v4 144 | with: 145 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 146 | path: | 147 | ${{ steps.cargo-dist.outputs.paths }} 148 | ${{ env.BUILD_MANIFEST_NAME }} 149 | 150 | # Build and package all the platform-agnostic(ish) things 151 | build-global-artifacts: 152 | needs: 153 | - plan 154 | - build-local-artifacts 155 | runs-on: "ubuntu-20.04" 156 | env: 157 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 158 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 159 | steps: 160 | - uses: actions/checkout@v4 161 | with: 162 | submodules: recursive 163 | - name: Install cargo-dist 164 | shell: bash 165 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 166 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 167 | - name: Fetch local artifacts 168 | uses: actions/download-artifact@v4 169 | with: 170 | pattern: artifacts-* 171 | path: target/distrib/ 172 | merge-multiple: true 173 | - id: cargo-dist 174 | shell: bash 175 | run: | 176 | cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 177 | echo "cargo dist ran successfully" 178 | 179 | # Parse out what we just built and upload it to scratch storage 180 | echo "paths<> "$GITHUB_OUTPUT" 181 | jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" 182 | echo "EOF" >> "$GITHUB_OUTPUT" 183 | 184 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 185 | - name: "Upload artifacts" 186 | uses: actions/upload-artifact@v4 187 | with: 188 | name: artifacts-build-global 189 | path: | 190 | ${{ steps.cargo-dist.outputs.paths }} 191 | ${{ env.BUILD_MANIFEST_NAME }} 192 | # Determines if we should publish/announce 193 | host: 194 | needs: 195 | - plan 196 | - build-local-artifacts 197 | - build-global-artifacts 198 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 199 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 200 | env: 201 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 202 | runs-on: "ubuntu-20.04" 203 | outputs: 204 | val: ${{ steps.host.outputs.manifest }} 205 | steps: 206 | - uses: actions/checkout@v4 207 | with: 208 | submodules: recursive 209 | - name: Install cargo-dist 210 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.11.1/cargo-dist-installer.sh | sh" 211 | # Fetch artifacts from scratch-storage 212 | - name: Fetch artifacts 213 | uses: actions/download-artifact@v4 214 | with: 215 | pattern: artifacts-* 216 | path: target/distrib/ 217 | merge-multiple: true 218 | # This is a harmless no-op for Github Releases, hosting for that happens in "announce" 219 | - id: host 220 | shell: bash 221 | run: | 222 | cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 223 | echo "artifacts uploaded and released successfully" 224 | cat dist-manifest.json 225 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 226 | - name: "Upload dist-manifest.json" 227 | uses: actions/upload-artifact@v4 228 | with: 229 | # Overwrite the previous copy 230 | name: artifacts-dist-manifest 231 | path: dist-manifest.json 232 | 233 | # Create a Github Release while uploading all files to it 234 | announce: 235 | needs: 236 | - plan 237 | - host 238 | # use "always() && ..." to allow us to wait for all publish jobs while 239 | # still allowing individual publish jobs to skip themselves (for prereleases). 240 | # "host" however must run to completion, no skipping allowed! 241 | if: ${{ always() && needs.host.result == 'success' }} 242 | runs-on: "ubuntu-20.04" 243 | env: 244 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 245 | steps: 246 | - uses: actions/checkout@v4 247 | with: 248 | submodules: recursive 249 | - name: "Download Github Artifacts" 250 | uses: actions/download-artifact@v4 251 | with: 252 | pattern: artifacts-* 253 | path: artifacts 254 | merge-multiple: true 255 | - name: Cleanup 256 | run: | 257 | # Remove the granular manifests 258 | rm -f artifacts/*-dist-manifest.json 259 | - name: Create Github Release 260 | uses: ncipollo/release-action@v1 261 | with: 262 | tag: ${{ needs.plan.outputs.tag }} 263 | name: ${{ fromJson(needs.host.outputs.val).announcement_title }} 264 | body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }} 265 | prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }} 266 | artifacts: "artifacts/*" 267 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.7.8" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 16 | dependencies = [ 17 | "getrandom", 18 | "once_cell", 19 | "version_check", 20 | ] 21 | 22 | [[package]] 23 | name = "anstream" 24 | version = "0.6.13" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 27 | dependencies = [ 28 | "anstyle", 29 | "anstyle-parse", 30 | "anstyle-query", 31 | "anstyle-wincon", 32 | "colorchoice", 33 | "utf8parse", 34 | ] 35 | 36 | [[package]] 37 | name = "anstyle" 38 | version = "1.0.6" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 41 | 42 | [[package]] 43 | name = "anstyle-parse" 44 | version = "0.2.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 47 | dependencies = [ 48 | "utf8parse", 49 | ] 50 | 51 | [[package]] 52 | name = "anstyle-query" 53 | version = "1.0.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 56 | dependencies = [ 57 | "windows-sys", 58 | ] 59 | 60 | [[package]] 61 | name = "anstyle-wincon" 62 | version = "3.0.2" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 65 | dependencies = [ 66 | "anstyle", 67 | "windows-sys", 68 | ] 69 | 70 | [[package]] 71 | name = "anyhow" 72 | version = "1.0.81" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" 75 | 76 | [[package]] 77 | name = "arrayvec" 78 | version = "0.7.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 81 | 82 | [[package]] 83 | name = "autocfg" 84 | version = "1.2.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 87 | 88 | [[package]] 89 | name = "base64" 90 | version = "0.21.7" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 93 | 94 | [[package]] 95 | name = "bitvec" 96 | version = "1.0.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 99 | dependencies = [ 100 | "funty", 101 | "radium", 102 | "tap", 103 | "wyz", 104 | ] 105 | 106 | [[package]] 107 | name = "borsh" 108 | version = "1.3.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" 111 | dependencies = [ 112 | "borsh-derive", 113 | "cfg_aliases", 114 | ] 115 | 116 | [[package]] 117 | name = "borsh-derive" 118 | version = "1.3.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" 121 | dependencies = [ 122 | "once_cell", 123 | "proc-macro-crate", 124 | "proc-macro2", 125 | "quote", 126 | "syn 2.0.55", 127 | "syn_derive", 128 | ] 129 | 130 | [[package]] 131 | name = "byte-unit" 132 | version = "5.1.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" 135 | dependencies = [ 136 | "rust_decimal", 137 | "serde", 138 | "utf8-width", 139 | ] 140 | 141 | [[package]] 142 | name = "bytecheck" 143 | version = "0.6.12" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" 146 | dependencies = [ 147 | "bytecheck_derive", 148 | "ptr_meta", 149 | "simdutf8", 150 | ] 151 | 152 | [[package]] 153 | name = "bytecheck_derive" 154 | version = "0.6.12" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" 157 | dependencies = [ 158 | "proc-macro2", 159 | "quote", 160 | "syn 1.0.109", 161 | ] 162 | 163 | [[package]] 164 | name = "bytes" 165 | version = "1.6.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 168 | 169 | [[package]] 170 | name = "cc" 171 | version = "1.0.90" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 174 | 175 | [[package]] 176 | name = "cfg-if" 177 | version = "1.0.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 180 | 181 | [[package]] 182 | name = "cfg_aliases" 183 | version = "0.1.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 186 | 187 | [[package]] 188 | name = "clap" 189 | version = "4.5.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 192 | dependencies = [ 193 | "clap_builder", 194 | "clap_derive", 195 | ] 196 | 197 | [[package]] 198 | name = "clap_builder" 199 | version = "4.5.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 202 | dependencies = [ 203 | "anstream", 204 | "anstyle", 205 | "clap_lex", 206 | "strsim", 207 | ] 208 | 209 | [[package]] 210 | name = "clap_derive" 211 | version = "4.5.4" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 214 | dependencies = [ 215 | "heck", 216 | "proc-macro2", 217 | "quote", 218 | "syn 2.0.55", 219 | ] 220 | 221 | [[package]] 222 | name = "clap_lex" 223 | version = "0.7.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 226 | 227 | [[package]] 228 | name = "colorchoice" 229 | version = "1.0.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 232 | 233 | [[package]] 234 | name = "console" 235 | version = "0.15.8" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 238 | dependencies = [ 239 | "encode_unicode", 240 | "lazy_static", 241 | "libc", 242 | "unicode-width", 243 | "windows-sys", 244 | ] 245 | 246 | [[package]] 247 | name = "crc32fast" 248 | version = "1.4.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 251 | dependencies = [ 252 | "cfg-if", 253 | ] 254 | 255 | [[package]] 256 | name = "csv" 257 | version = "1.3.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" 260 | dependencies = [ 261 | "csv-core", 262 | "itoa", 263 | "ryu", 264 | "serde", 265 | ] 266 | 267 | [[package]] 268 | name = "csv-core" 269 | version = "0.1.11" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" 272 | dependencies = [ 273 | "memchr", 274 | ] 275 | 276 | [[package]] 277 | name = "encode_unicode" 278 | version = "0.3.6" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 281 | 282 | [[package]] 283 | name = "equivalent" 284 | version = "1.0.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 287 | 288 | [[package]] 289 | name = "exponential-backoff" 290 | version = "1.2.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "47f78d87d930eee4b5686a2ab032de499c72bd1e954b84262bb03492a0f932cd" 293 | dependencies = [ 294 | "rand", 295 | ] 296 | 297 | [[package]] 298 | name = "flate2" 299 | version = "1.0.28" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 302 | dependencies = [ 303 | "crc32fast", 304 | "miniz_oxide", 305 | ] 306 | 307 | [[package]] 308 | name = "form_urlencoded" 309 | version = "1.2.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 312 | dependencies = [ 313 | "percent-encoding", 314 | ] 315 | 316 | [[package]] 317 | name = "funty" 318 | version = "2.0.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 321 | 322 | [[package]] 323 | name = "getrandom" 324 | version = "0.2.12" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 327 | dependencies = [ 328 | "cfg-if", 329 | "libc", 330 | "wasi", 331 | ] 332 | 333 | [[package]] 334 | name = "hashbrown" 335 | version = "0.12.3" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 338 | dependencies = [ 339 | "ahash", 340 | ] 341 | 342 | [[package]] 343 | name = "hashbrown" 344 | version = "0.14.3" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 347 | 348 | [[package]] 349 | name = "heck" 350 | version = "0.5.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 353 | 354 | [[package]] 355 | name = "idna" 356 | version = "0.5.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 359 | dependencies = [ 360 | "unicode-bidi", 361 | "unicode-normalization", 362 | ] 363 | 364 | [[package]] 365 | name = "indexmap" 366 | version = "2.2.6" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 369 | dependencies = [ 370 | "equivalent", 371 | "hashbrown 0.14.3", 372 | ] 373 | 374 | [[package]] 375 | name = "indicatif" 376 | version = "0.17.8" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 379 | dependencies = [ 380 | "console", 381 | "instant", 382 | "number_prefix", 383 | "portable-atomic", 384 | "unicode-width", 385 | ] 386 | 387 | [[package]] 388 | name = "instant" 389 | version = "0.1.12" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 392 | dependencies = [ 393 | "cfg-if", 394 | ] 395 | 396 | [[package]] 397 | name = "itoa" 398 | version = "1.0.11" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 401 | 402 | [[package]] 403 | name = "lazy_static" 404 | version = "1.4.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 407 | 408 | [[package]] 409 | name = "libc" 410 | version = "0.2.153" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 413 | 414 | [[package]] 415 | name = "log" 416 | version = "0.4.21" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 419 | 420 | [[package]] 421 | name = "meilisearch-importer" 422 | version = "0.2.2" 423 | dependencies = [ 424 | "anyhow", 425 | "byte-unit", 426 | "clap", 427 | "csv", 428 | "exponential-backoff", 429 | "flate2", 430 | "indicatif", 431 | "serde_json", 432 | "ureq", 433 | ] 434 | 435 | [[package]] 436 | name = "memchr" 437 | version = "2.7.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 440 | 441 | [[package]] 442 | name = "miniz_oxide" 443 | version = "0.7.2" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 446 | dependencies = [ 447 | "adler", 448 | ] 449 | 450 | [[package]] 451 | name = "num-traits" 452 | version = "0.2.18" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 455 | dependencies = [ 456 | "autocfg", 457 | ] 458 | 459 | [[package]] 460 | name = "number_prefix" 461 | version = "0.4.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 464 | 465 | [[package]] 466 | name = "once_cell" 467 | version = "1.19.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 470 | 471 | [[package]] 472 | name = "percent-encoding" 473 | version = "2.3.1" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 476 | 477 | [[package]] 478 | name = "portable-atomic" 479 | version = "1.6.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 482 | 483 | [[package]] 484 | name = "ppv-lite86" 485 | version = "0.2.17" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 488 | 489 | [[package]] 490 | name = "proc-macro-crate" 491 | version = "3.1.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" 494 | dependencies = [ 495 | "toml_edit", 496 | ] 497 | 498 | [[package]] 499 | name = "proc-macro-error" 500 | version = "1.0.4" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 503 | dependencies = [ 504 | "proc-macro-error-attr", 505 | "proc-macro2", 506 | "quote", 507 | "version_check", 508 | ] 509 | 510 | [[package]] 511 | name = "proc-macro-error-attr" 512 | version = "1.0.4" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 515 | dependencies = [ 516 | "proc-macro2", 517 | "quote", 518 | "version_check", 519 | ] 520 | 521 | [[package]] 522 | name = "proc-macro2" 523 | version = "1.0.79" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 526 | dependencies = [ 527 | "unicode-ident", 528 | ] 529 | 530 | [[package]] 531 | name = "ptr_meta" 532 | version = "0.1.4" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 535 | dependencies = [ 536 | "ptr_meta_derive", 537 | ] 538 | 539 | [[package]] 540 | name = "ptr_meta_derive" 541 | version = "0.1.4" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 544 | dependencies = [ 545 | "proc-macro2", 546 | "quote", 547 | "syn 1.0.109", 548 | ] 549 | 550 | [[package]] 551 | name = "quote" 552 | version = "1.0.35" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 555 | dependencies = [ 556 | "proc-macro2", 557 | ] 558 | 559 | [[package]] 560 | name = "radium" 561 | version = "0.7.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 564 | 565 | [[package]] 566 | name = "rand" 567 | version = "0.8.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 570 | dependencies = [ 571 | "libc", 572 | "rand_chacha", 573 | "rand_core", 574 | ] 575 | 576 | [[package]] 577 | name = "rand_chacha" 578 | version = "0.3.1" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 581 | dependencies = [ 582 | "ppv-lite86", 583 | "rand_core", 584 | ] 585 | 586 | [[package]] 587 | name = "rand_core" 588 | version = "0.6.4" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 591 | dependencies = [ 592 | "getrandom", 593 | ] 594 | 595 | [[package]] 596 | name = "rend" 597 | version = "0.4.2" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" 600 | dependencies = [ 601 | "bytecheck", 602 | ] 603 | 604 | [[package]] 605 | name = "ring" 606 | version = "0.17.8" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 609 | dependencies = [ 610 | "cc", 611 | "cfg-if", 612 | "getrandom", 613 | "libc", 614 | "spin", 615 | "untrusted", 616 | "windows-sys", 617 | ] 618 | 619 | [[package]] 620 | name = "rkyv" 621 | version = "0.7.44" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" 624 | dependencies = [ 625 | "bitvec", 626 | "bytecheck", 627 | "bytes", 628 | "hashbrown 0.12.3", 629 | "ptr_meta", 630 | "rend", 631 | "rkyv_derive", 632 | "seahash", 633 | "tinyvec", 634 | "uuid", 635 | ] 636 | 637 | [[package]] 638 | name = "rkyv_derive" 639 | version = "0.7.44" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" 642 | dependencies = [ 643 | "proc-macro2", 644 | "quote", 645 | "syn 1.0.109", 646 | ] 647 | 648 | [[package]] 649 | name = "rust_decimal" 650 | version = "1.34.3" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" 653 | dependencies = [ 654 | "arrayvec", 655 | "borsh", 656 | "bytes", 657 | "num-traits", 658 | "rand", 659 | "rkyv", 660 | "serde", 661 | "serde_json", 662 | ] 663 | 664 | [[package]] 665 | name = "rustls" 666 | version = "0.22.3" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" 669 | dependencies = [ 670 | "log", 671 | "ring", 672 | "rustls-pki-types", 673 | "rustls-webpki", 674 | "subtle", 675 | "zeroize", 676 | ] 677 | 678 | [[package]] 679 | name = "rustls-pki-types" 680 | version = "1.4.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" 683 | 684 | [[package]] 685 | name = "rustls-webpki" 686 | version = "0.102.2" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" 689 | dependencies = [ 690 | "ring", 691 | "rustls-pki-types", 692 | "untrusted", 693 | ] 694 | 695 | [[package]] 696 | name = "ryu" 697 | version = "1.0.17" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 700 | 701 | [[package]] 702 | name = "seahash" 703 | version = "4.1.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 706 | 707 | [[package]] 708 | name = "serde" 709 | version = "1.0.197" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 712 | dependencies = [ 713 | "serde_derive", 714 | ] 715 | 716 | [[package]] 717 | name = "serde_derive" 718 | version = "1.0.197" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 721 | dependencies = [ 722 | "proc-macro2", 723 | "quote", 724 | "syn 2.0.55", 725 | ] 726 | 727 | [[package]] 728 | name = "serde_json" 729 | version = "1.0.115" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" 732 | dependencies = [ 733 | "indexmap", 734 | "itoa", 735 | "ryu", 736 | "serde", 737 | ] 738 | 739 | [[package]] 740 | name = "simdutf8" 741 | version = "0.1.4" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" 744 | 745 | [[package]] 746 | name = "spin" 747 | version = "0.9.8" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 750 | 751 | [[package]] 752 | name = "strsim" 753 | version = "0.11.0" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 756 | 757 | [[package]] 758 | name = "subtle" 759 | version = "2.5.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 762 | 763 | [[package]] 764 | name = "syn" 765 | version = "1.0.109" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 768 | dependencies = [ 769 | "proc-macro2", 770 | "quote", 771 | "unicode-ident", 772 | ] 773 | 774 | [[package]] 775 | name = "syn" 776 | version = "2.0.55" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" 779 | dependencies = [ 780 | "proc-macro2", 781 | "quote", 782 | "unicode-ident", 783 | ] 784 | 785 | [[package]] 786 | name = "syn_derive" 787 | version = "0.1.8" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" 790 | dependencies = [ 791 | "proc-macro-error", 792 | "proc-macro2", 793 | "quote", 794 | "syn 2.0.55", 795 | ] 796 | 797 | [[package]] 798 | name = "tap" 799 | version = "1.0.1" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 802 | 803 | [[package]] 804 | name = "tinyvec" 805 | version = "1.6.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 808 | dependencies = [ 809 | "tinyvec_macros", 810 | ] 811 | 812 | [[package]] 813 | name = "tinyvec_macros" 814 | version = "0.1.1" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 817 | 818 | [[package]] 819 | name = "toml_datetime" 820 | version = "0.6.5" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 823 | 824 | [[package]] 825 | name = "toml_edit" 826 | version = "0.21.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" 829 | dependencies = [ 830 | "indexmap", 831 | "toml_datetime", 832 | "winnow", 833 | ] 834 | 835 | [[package]] 836 | name = "unicode-bidi" 837 | version = "0.3.15" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 840 | 841 | [[package]] 842 | name = "unicode-ident" 843 | version = "1.0.12" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 846 | 847 | [[package]] 848 | name = "unicode-normalization" 849 | version = "0.1.23" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 852 | dependencies = [ 853 | "tinyvec", 854 | ] 855 | 856 | [[package]] 857 | name = "unicode-width" 858 | version = "0.1.11" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 861 | 862 | [[package]] 863 | name = "untrusted" 864 | version = "0.9.0" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 867 | 868 | [[package]] 869 | name = "ureq" 870 | version = "2.9.6" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" 873 | dependencies = [ 874 | "base64", 875 | "flate2", 876 | "log", 877 | "once_cell", 878 | "rustls", 879 | "rustls-pki-types", 880 | "rustls-webpki", 881 | "url", 882 | "webpki-roots", 883 | ] 884 | 885 | [[package]] 886 | name = "url" 887 | version = "2.5.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 890 | dependencies = [ 891 | "form_urlencoded", 892 | "idna", 893 | "percent-encoding", 894 | ] 895 | 896 | [[package]] 897 | name = "utf8-width" 898 | version = "0.1.7" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" 901 | 902 | [[package]] 903 | name = "utf8parse" 904 | version = "0.2.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 907 | 908 | [[package]] 909 | name = "uuid" 910 | version = "1.8.0" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" 913 | 914 | [[package]] 915 | name = "version_check" 916 | version = "0.9.4" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 919 | 920 | [[package]] 921 | name = "wasi" 922 | version = "0.11.0+wasi-snapshot-preview1" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 925 | 926 | [[package]] 927 | name = "webpki-roots" 928 | version = "0.26.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" 931 | dependencies = [ 932 | "rustls-pki-types", 933 | ] 934 | 935 | [[package]] 936 | name = "windows-sys" 937 | version = "0.52.0" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 940 | dependencies = [ 941 | "windows-targets", 942 | ] 943 | 944 | [[package]] 945 | name = "windows-targets" 946 | version = "0.52.4" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 949 | dependencies = [ 950 | "windows_aarch64_gnullvm", 951 | "windows_aarch64_msvc", 952 | "windows_i686_gnu", 953 | "windows_i686_msvc", 954 | "windows_x86_64_gnu", 955 | "windows_x86_64_gnullvm", 956 | "windows_x86_64_msvc", 957 | ] 958 | 959 | [[package]] 960 | name = "windows_aarch64_gnullvm" 961 | version = "0.52.4" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 964 | 965 | [[package]] 966 | name = "windows_aarch64_msvc" 967 | version = "0.52.4" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 970 | 971 | [[package]] 972 | name = "windows_i686_gnu" 973 | version = "0.52.4" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 976 | 977 | [[package]] 978 | name = "windows_i686_msvc" 979 | version = "0.52.4" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 982 | 983 | [[package]] 984 | name = "windows_x86_64_gnu" 985 | version = "0.52.4" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 988 | 989 | [[package]] 990 | name = "windows_x86_64_gnullvm" 991 | version = "0.52.4" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 994 | 995 | [[package]] 996 | name = "windows_x86_64_msvc" 997 | version = "0.52.4" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 1000 | 1001 | [[package]] 1002 | name = "winnow" 1003 | version = "0.5.40" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 1006 | dependencies = [ 1007 | "memchr", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "wyz" 1012 | version = "0.5.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 1015 | dependencies = [ 1016 | "tap", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "zeroize" 1021 | version = "1.7.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 1024 | --------------------------------------------------------------------------------