├── tests ├── common │ ├── mod.rs │ ├── settings.rs │ └── server.rs ├── tests.rs ├── fixtures │ ├── transitive2.json │ ├── schema_backward_compatible.json │ ├── transitive3.json │ ├── transitive1.json │ ├── schema2.json │ ├── schema.json │ ├── schema_full_compatible.json │ └── schema_forward_compatible.json ├── schemas.rs ├── config.rs ├── db │ └── mod.rs ├── compatibility.rs └── subject.rs ├── rust-toolchain.toml ├── migrations ├── 2018-12-13-153527_create_subjects │ ├── down.sql │ └── up.sql ├── 2018-12-14-163507_create_schemas │ ├── down.sql │ └── up.sql ├── 2018-12-18-105635_create_configs │ ├── down.sql │ └── up.sql ├── 2018-12-16-123727_create_schema_versions │ ├── down.sql │ └── up.sql └── 00000000000000_diesel_initial_setup │ ├── down.sql │ └── up.sql ├── src ├── lib.rs ├── db │ ├── mod.rs │ ├── models │ │ ├── mod.rs │ │ ├── schema.rs │ │ ├── subjects.rs │ │ ├── schemas.rs │ │ ├── configs.rs │ │ └── schema_versions.rs │ └── connection.rs ├── middleware │ ├── mod.rs │ ├── verify_headers.rs │ └── verify_auth.rs ├── health │ └── mod.rs ├── api │ ├── version.rs │ ├── mod.rs │ ├── compatibility.rs │ ├── configs.rs │ ├── schemas.rs │ ├── subjects.rs │ └── errors.rs ├── bin │ └── main.rs └── app │ └── mod.rs ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── diesel.toml ├── CHANGELOG.md ├── docker-compose.yml ├── TODO ├── LICENSE ├── Cargo.toml ├── README.md └── Cargo.lock /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod server; 2 | pub mod settings; 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /migrations/2018-12-13-153527_create_subjects/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` -------------------------------------------------------------------------------- /migrations/2018-12-14-163507_create_schemas/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` -------------------------------------------------------------------------------- /migrations/2018-12-18-105635_create_configs/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` -------------------------------------------------------------------------------- /migrations/2018-12-16-123727_create_schema_versions/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod app; 3 | pub mod db; 4 | pub mod health; 5 | pub mod middleware; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.cargo/ 3 | **/*.rs.bk 4 | rls*.log 5 | development-db 6 | .projectile 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /src/db/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::connection::{DbConnection, DbManage, DbPool}; 2 | 3 | mod connection; 4 | pub mod models; 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/db/models/schema.rs" 6 | -------------------------------------------------------------------------------- /src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::verify_auth::VerifyAuthorization; 2 | pub use self::verify_headers::VerifyAcceptHeader; 3 | 4 | mod verify_auth; 5 | mod verify_headers; 6 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_json; 3 | 4 | mod common; 5 | mod compatibility; 6 | mod config; 7 | mod db; 8 | mod schemas; 9 | mod subject; 10 | -------------------------------------------------------------------------------- /tests/common/settings.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | pub fn get_schema_registry_password() -> String { 4 | env::var("SCHEMA_REGISTRY_PASSWORD").unwrap_or_else(|_| "test_password".to_string()) 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.1.2] - 2019/05/11 4 | 5 | ### Added 6 | 7 | - /metrics endpoint that exposes Prometheus metrics 8 | - CHANGELOG.md 9 | 10 | ### Changed 11 | 12 | - Simplified tests 13 | -------------------------------------------------------------------------------- /tests/fixtures/transitive2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": [ 5 | { 6 | "type": "string", 7 | "name": "field1" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgresql_14: 3 | image: postgres:14 4 | ports: 5 | - 5432:5432 6 | environment: 7 | POSTGRES_PASSWORD: "password" 8 | command: postgres -c max_connections=1000 9 | -------------------------------------------------------------------------------- /src/health/mod.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpResponse, Responder}; 2 | 3 | pub async fn status() -> impl Responder { 4 | HttpResponse::Ok() 5 | .content_type("application/json") 6 | .body("{\"status\": \"healthy\"}") 7 | } 8 | -------------------------------------------------------------------------------- /src/api/version.rs: -------------------------------------------------------------------------------- 1 | pub trait VersionLimit { 2 | fn within_limits(&self) -> bool; 3 | } 4 | 5 | impl VersionLimit for u32 { 6 | fn within_limits(&self) -> bool { 7 | *self > 0 && *self < 2_147_483_648 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/db/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::configs::*; 2 | pub use self::schema_versions::*; 3 | pub use self::schemas::*; 4 | pub use self::subjects::*; 5 | 6 | pub mod schema; 7 | 8 | mod configs; 9 | mod schema_versions; 10 | mod schemas; 11 | mod subjects; 12 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::compatibility::*; 2 | pub use self::configs::*; 3 | pub use self::schemas::*; 4 | pub use self::subjects::*; 5 | 6 | mod compatibility; 7 | mod configs; 8 | pub mod errors; 9 | mod schemas; 10 | mod subjects; 11 | pub mod version; 12 | -------------------------------------------------------------------------------- /tests/fixtures/schema_backward_compatible.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": 5 | [ 6 | { 7 | "type": "string", 8 | "name": "field1", 9 | "default": "" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/transitive3.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": [ 5 | { 6 | "type": "string", 7 | "name": "field1" 8 | }, 9 | { 10 | "type": "int", 11 | "name": "field2", 12 | "default": "0" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/transitive1.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": [ 5 | { 6 | "type": "string", 7 | "name": "field1" 8 | }, 9 | { 10 | "type": "string", 11 | "name": "field2", 12 | "default": "foo" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/schema2.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test2", 4 | "fields": 5 | [ 6 | { 7 | "type": "string", 8 | "name": "field1" 9 | }, 10 | { 11 | "type": "int", 12 | "name": "field2" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - [ ] make sure we check for avro compatibility when registering (https://docs.confluent.io/current/schema-registry/develop/api.html#post--subjects-(string-%20subject)-versions) 2 | - [ ] test get_subject_version_schema (currently we assume copy&paste so we only test the other path) 3 | - [ ] /_/health_check should have endoint to check status of DB? -------------------------------------------------------------------------------- /tests/fixtures/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": 5 | [ 6 | { 7 | "type": "string", 8 | "name": "field1", 9 | "default": "" 10 | }, 11 | { 12 | "type": "string", 13 | "name": "field2" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/schema_full_compatible.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": 5 | [ 6 | { 7 | "type": "string", 8 | "name": "field2" 9 | }, 10 | { 11 | "type": "string", 12 | "name": "extra", 13 | "default": "" 14 | } 15 | 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /migrations/00000000000000_diesel_initial_setup/down.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); 6 | DROP FUNCTION IF EXISTS diesel_set_updated_at(); 7 | -------------------------------------------------------------------------------- /migrations/2018-12-16-123727_create_schema_versions/up.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE schema_versions_id_seq; 2 | CREATE TABLE schema_versions ( 3 | id BIGINT PRIMARY KEY DEFAULT nextval('schema_versions_id_seq'::regclass), 4 | version INTEGER DEFAULT 1, 5 | subject_id BIGINT NOT NULL, 6 | schema_id BIGINT NOT NULL 7 | ); 8 | 9 | CREATE UNIQUE INDEX index_schema_versions_on_subject_id_and_version ON schema_versions(subject_id, version); 10 | -------------------------------------------------------------------------------- /migrations/2018-12-13-153527_create_subjects/up.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE subjects_id_seq; 2 | CREATE TABLE subjects ( 3 | id BIGINT PRIMARY KEY DEFAULT nextval('subjects_id_seq'::regclass), 4 | name TEXT NOT NULL, 5 | created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 6 | updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL 7 | ); 8 | 9 | CREATE UNIQUE INDEX index_subjects_on_name ON subjects(name); 10 | SELECT diesel_manage_updated_at('subjects'); 11 | -------------------------------------------------------------------------------- /tests/fixtures/schema_forward_compatible.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "record", 3 | "name": "test", 4 | "fields": 5 | [ 6 | { 7 | "type": "string", 8 | "name": "field1", 9 | "default": "" 10 | }, 11 | { 12 | "type": "string", 13 | "name": "field2" 14 | }, 15 | { 16 | "type": "string", 17 | "name": "extra" 18 | } 19 | 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /migrations/2018-12-18-105635_create_configs/up.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE configs_id_seq; 2 | CREATE TABLE configs ( 3 | id BIGINT PRIMARY KEY DEFAULT nextval('configs_id_seq'::regclass), 4 | compatibility CHARACTER VARYING, 5 | created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 6 | updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 7 | subject_id BIGINT 8 | ); 9 | 10 | CREATE UNIQUE INDEX index_configs_on_subject_id ON configs(subject_id); 11 | 12 | SELECT diesel_manage_updated_at('configs'); 13 | -------------------------------------------------------------------------------- /migrations/2018-12-14-163507_create_schemas/up.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE schemas_id_seq; 2 | CREATE TABLE schemas ( 3 | id BIGINT PRIMARY KEY DEFAULT nextval('schemas_id_seq'::regclass), 4 | fingerprint CHARACTER VARYING NOT NULL, 5 | json TEXT NOT NULL, 6 | created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 7 | updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, 8 | fingerprint2 CHARACTER VARYING 9 | ); 10 | 11 | CREATE UNIQUE INDEX index_schemas_on_fingerprint ON schemas(fingerprint); 12 | CREATE UNIQUE INDEX index_schemas_on_fingerprint2 ON schemas(fingerprint2); 13 | 14 | SELECT diesel_manage_updated_at('schemas'); 15 | -------------------------------------------------------------------------------- /src/db/connection.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use diesel::pg::PgConnection; 4 | use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; 5 | 6 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 7 | 8 | pub type DbPool = Pool>; 9 | pub type DbConnection = PooledConnection>; 10 | 11 | pub trait DbManage { 12 | fn new_pool(max_size: Option) -> Self; 13 | fn connection(&self) -> Result; 14 | } 15 | 16 | impl DbManage for DbPool { 17 | fn new_pool(max_size: Option) -> Self { 18 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 19 | let manager = ConnectionManager::::new(database_url); 20 | Pool::builder() 21 | .max_size(max_size.unwrap_or(10)) 22 | .build(manager) 23 | .expect("Failed to create pool.") 24 | } 25 | 26 | fn connection(&self) -> Result { 27 | self.get() 28 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/db/models/schema.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | configs (id) { 5 | id -> Int8, 6 | compatibility -> Nullable, 7 | created_at -> Timestamp, 8 | updated_at -> Timestamp, 9 | subject_id -> Nullable, 10 | } 11 | } 12 | 13 | diesel::table! { 14 | schema_versions (id) { 15 | id -> Int8, 16 | version -> Nullable, 17 | subject_id -> Int8, 18 | schema_id -> Int8, 19 | } 20 | } 21 | 22 | diesel::table! { 23 | schemas (id) { 24 | id -> Int8, 25 | fingerprint -> Varchar, 26 | json -> Text, 27 | created_at -> Timestamp, 28 | updated_at -> Timestamp, 29 | fingerprint2 -> Nullable, 30 | } 31 | } 32 | 33 | diesel::table! { 34 | subjects (id) { 35 | id -> Int8, 36 | name -> Text, 37 | created_at -> Timestamp, 38 | updated_at -> Timestamp, 39 | } 40 | } 41 | 42 | diesel::allow_tables_to_appear_in_same_query!( 43 | configs, 44 | schema_versions, 45 | schemas, 46 | subjects, 47 | ); 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Norberto Lopes 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avro-schema-registry" 3 | version = "0.1.4" 4 | authors = ["Norberto Lopes "] 5 | edition = "2021" 6 | autotests = false 7 | 8 | [badges] 9 | travis-ci = { repository = "nlopes/avro-schema-registry", branch = "master" } 10 | 11 | [[test]] 12 | name = "integration" 13 | path = "tests/tests.rs" 14 | 15 | [lib] 16 | name = "avro_schema_registry" 17 | path = "src/lib.rs" 18 | 19 | [[bin]] 20 | name = "avro-schema-registry" 21 | path = "src/bin/main.rs" 22 | test = false 23 | doc = false 24 | 25 | [dependencies] 26 | actix = "0.13" 27 | actix-threadpool = "0.3" 28 | actix-web = "4" 29 | actix-web-prom = "0.9" 30 | avro-rs = { git = "https://github.com/apache/avro", package = "apache-avro", version = "0.18" } 31 | base64 = "0.22" 32 | chrono = { version = "0.4", features = ["serde"] } 33 | diesel = { version = "2", features = ["postgres", "chrono", "r2d2"] } 34 | env_logger = "0.11" 35 | futures = "0.3" 36 | log = "0.4" 37 | sha2 = "0.10" 38 | sentry = { version = "0.36", features = ["panic"] } 39 | serde = "1" 40 | serde_derive = "1" 41 | serde_json = "1" 42 | thiserror = "2" 43 | 44 | [dev-dependencies] 45 | actix-rt = "2" 46 | actix-test = "0.1" 47 | awc = "3" 48 | regex = "1" 49 | -------------------------------------------------------------------------------- /migrations/00000000000000_diesel_initial_setup/up.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | 6 | 7 | 8 | -- Sets up a trigger for the given table to automatically set a column called 9 | -- `updated_at` whenever the row is modified (unless `updated_at` was included 10 | -- in the modified columns) 11 | -- 12 | -- # Example 13 | -- 14 | -- ```sql 15 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); 16 | -- 17 | -- SELECT diesel_manage_updated_at('users'); 18 | -- ``` 19 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ 20 | BEGIN 21 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s 22 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); 23 | END; 24 | $$ LANGUAGE plpgsql; 25 | 26 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ 27 | BEGIN 28 | IF ( 29 | NEW IS DISTINCT FROM OLD AND 30 | NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at 31 | ) THEN 32 | NEW.updated_at := current_timestamp; 33 | END IF; 34 | RETURN NEW; 35 | END; 36 | $$ LANGUAGE plpgsql; 37 | -------------------------------------------------------------------------------- /tests/schemas.rs: -------------------------------------------------------------------------------- 1 | use actix_web::http; 2 | 3 | use crate::common::server::setup; 4 | use crate::db::DbAuxOperations; 5 | 6 | #[actix_rt::test] 7 | async fn test_get_schema_without_schema() { 8 | let (server, _) = setup(); 9 | 10 | // it returns 404 with message 11 | server 12 | .test( 13 | http::Method::GET, 14 | "/schemas/ids/1", 15 | None, 16 | http::StatusCode::NOT_FOUND, 17 | r#"\{"error_code":40403,"message":"Schema not found"\}"#, 18 | ) 19 | .await; 20 | } 21 | 22 | #[actix_rt::test] 23 | async fn test_get_schema_with_schema() { 24 | let (server, mut conn) = setup(); 25 | 26 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 27 | let schema = conn.register_schema(String::from("subject1"), schema_s.to_string()); 28 | 29 | // it returns schema 30 | server 31 | .test( 32 | http::Method::GET, 33 | &format!("/schemas/ids/{}", schema.id), 34 | None, 35 | http::StatusCode::OK, 36 | r#"\{"schema":"\{ \\"type\\": \\"record\\", \\"name\\": \\"test\\", \\"fields\\": \[ \{ \\"type\\": \\"string\\", \\"name\\": \\"field1\\", \\"default\\": \\"\\" \}, \{ \\"type\\": \\"string\\", \\"name\\": \\"field2\\" \} \]\}"\}"# 37 | ) 38 | .await; 39 | } 40 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use actix_web::{ 4 | middleware::{Compat, Logger}, 5 | web::Data, 6 | App, HttpServer, 7 | }; 8 | use actix_web_prom::PrometheusMetricsBuilder; 9 | use sentry::integrations::panic as sentry_panic; 10 | use sentry::IntoDsn; 11 | 12 | use avro_schema_registry::app; 13 | use avro_schema_registry::db::{DbManage, DbPool}; 14 | 15 | #[actix_web::main] 16 | async fn main() -> std::io::Result<()> { 17 | env::set_var("RUST_LOG", "actix_web=debug,avro_schema_registry=debug"); 18 | env::set_var("RUST_BACKTRACE", "1"); 19 | env_logger::init(); 20 | 21 | let _sentry_client = sentry::init(sentry::ClientOptions { 22 | dsn: env::var("SENTRY_URL") 23 | .ok() 24 | .into_dsn() 25 | .expect("Invalid Sentry DSN"), 26 | release: Some(std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))), 27 | ..Default::default() 28 | }); 29 | 30 | let _integration = sentry_panic::PanicIntegration::default().add_extractor(|_info| None); 31 | let prometheus = PrometheusMetricsBuilder::new("avro_schema_registry") 32 | .endpoint("/_/metrics") 33 | .build() 34 | .expect("Failed to instantiate Prometheus metrics"); 35 | 36 | let host = env::var("DEFAULT_HOST").unwrap_or_else(|_| "127.0.0.1:8080".to_string()); 37 | log::info!("Starting server at {}", host); 38 | 39 | HttpServer::new(move || { 40 | let db_pool = DbPool::new_pool(None); 41 | 42 | App::new() 43 | .wrap(Compat::new(Logger::default())) 44 | .wrap(prometheus.clone()) 45 | .configure(app::monitoring_routing) 46 | .app_data(Data::new(db_pool)) 47 | .configure(app::api_routing) 48 | }) 49 | .bind(host)? 50 | .shutdown_timeout(2) 51 | .run() 52 | .await 53 | } 54 | -------------------------------------------------------------------------------- /src/api/compatibility.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use actix_web::{ 4 | web::{Data, Json, Path}, 5 | HttpResponse, Responder, 6 | }; 7 | use log::info; 8 | use serde::Serialize; 9 | 10 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 11 | use crate::api::SchemaBody; 12 | use crate::db::models::{CompatibilityLevel, Config, Schema}; 13 | use crate::db::{DbManage, DbPool}; 14 | 15 | pub async fn check_compatibility( 16 | info: Path<(String, u32)>, 17 | body: Json, 18 | db: Data, 19 | ) -> impl Responder { 20 | let (subject, version) = info.into_inner(); 21 | let schema = body.into_inner().schema; 22 | info!("method=post,subject={},version={}", subject, version); 23 | 24 | let mut conn = db.connection()?; 25 | let sv_response = crate::api::subjects::get_subject_version_from_db( 26 | &mut conn, 27 | subject.clone(), 28 | Some(version), 29 | )?; 30 | let compatibility = Config::get_with_subject_name(&mut conn, subject)?; 31 | if let Ok(compat) = CompatibilityLevel::from_str(&compatibility) { 32 | if let Ok(is_compatible) = 33 | SchemaCompatibility::is_compatible(&sv_response.schema, &schema, compat) 34 | { 35 | Ok(HttpResponse::Ok().json(SchemaCompatibility { is_compatible })) 36 | } else { 37 | Err(ApiError::new(ApiAvroErrorCode::InvalidAvroSchema)) 38 | } 39 | } else { 40 | Err(ApiError::new(ApiAvroErrorCode::InvalidAvroSchema)) 41 | } 42 | } 43 | 44 | #[derive(Debug, Serialize)] 45 | struct SchemaCompatibility { 46 | is_compatible: bool, 47 | } 48 | 49 | impl SchemaCompatibility { 50 | fn is_compatible( 51 | old: &str, 52 | new: &str, 53 | compatibility: CompatibilityLevel, 54 | ) -> Result { 55 | match compatibility { 56 | CompatibilityLevel::CompatNone => Ok(true), 57 | CompatibilityLevel::Backward => Schema::is_compatible(new, old), 58 | CompatibilityLevel::Forward => Schema::is_compatible(old, new), 59 | CompatibilityLevel::Full => { 60 | Ok(Schema::is_compatible(old, new)? && Schema::is_compatible(new, old)?) 61 | } 62 | _ => unimplemented!(), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/configs.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | web::{Data, Json, Path}, 3 | HttpResponse, Responder, 4 | }; 5 | use log::info; 6 | 7 | use crate::db::models::{Config, ConfigCompatibility, SetConfig}; 8 | use crate::db::{DbManage, DbPool}; 9 | 10 | pub async fn get_config(db: Data) -> impl Responder { 11 | info!("path=/config,method=get"); 12 | 13 | let mut conn = db.connection()?; 14 | match Config::get_global_compatibility(&mut conn).and_then(ConfigCompatibility::new) { 15 | Ok(config) => Ok(HttpResponse::Ok().json(config)), 16 | Err(e) => Err(e), 17 | } 18 | } 19 | 20 | pub async fn put_config(body: Json, db: Data) -> impl Responder { 21 | let compatibility = body.compatibility; 22 | info!("method=put,compatibility={}", compatibility); 23 | 24 | let mut conn = db.connection()?; 25 | match Config::set_global_compatibility(&mut conn, &compatibility.valid()?.to_string()) 26 | .and_then(ConfigCompatibility::new) 27 | { 28 | Ok(config) => Ok(HttpResponse::Ok().json(config)), 29 | Err(e) => Err(e), 30 | } 31 | } 32 | 33 | /// Get compatibility level for a subject. 34 | pub async fn get_subject_config(subject_path: Path, db: Data) -> impl Responder { 35 | let subject = subject_path.into_inner(); 36 | info!("method=get,subject={}", subject); 37 | 38 | let mut conn = db.connection()?; 39 | match Config::get_with_subject_name(&mut conn, subject).and_then(ConfigCompatibility::new) { 40 | Ok(config) => Ok(HttpResponse::Ok().json(config)), 41 | Err(e) => Err(e), 42 | } 43 | } 44 | 45 | /// Update compatibility level for the specified subject. 46 | /// 47 | /// *Note:* The confluent schema registry does not return "Subject not found" if the 48 | /// subject does not exist, due to the way they map configs to subjects. We map them 49 | /// internally to subject_id's therefore, we can *and will* return "Schema not found" if 50 | /// no subject is found with the given name. 51 | pub async fn put_subject_config( 52 | subject_path: Path, 53 | body: Json, 54 | db: Data, 55 | ) -> impl Responder { 56 | let subject = subject_path.into_inner(); 57 | let compatibility = body.compatibility; 58 | info!( 59 | "method=put,subject={},compatibility={}", 60 | subject, compatibility 61 | ); 62 | 63 | let mut conn = db.connection()?; 64 | match Config::set_with_subject_name(&mut conn, subject, compatibility.valid()?.to_string()) 65 | .and_then(ConfigCompatibility::new) 66 | { 67 | Ok(config) => Ok(HttpResponse::Ok().json(config)), 68 | Err(e) => Err(e), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: '*' 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | continue-on-error: ${{ matrix.nightly }} 16 | env: 17 | DATABASE_URL: postgres://postgres:postgres@localhost:5432/diesel_testing 18 | RUST_TEST_THREADS: 1 19 | SCHEMA_REGISTRY_PASSWORD: silly_password 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | toolchain: [ 'stable' ] 25 | nightly: [false] 26 | include: 27 | - toolchain: 'nightly' 28 | nightly: true 29 | 30 | services: 31 | postgres: 32 | image: postgres 33 | env: 34 | POSTGRES_PASSWORD: postgres 35 | ports: 36 | - 5432:5432 37 | options: >- 38 | --health-cmd pg_isready 39 | --health-interval 10s 40 | --health-timeout 5s 41 | --health-retries 5 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - name: Install latest nightly 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: ${{ matrix.toolchain }} 51 | override: true 52 | components: rustfmt, clippy 53 | 54 | - name: Cache cargo registry 55 | uses: actions/cache@v2 56 | with: 57 | path: | 58 | ~/.cargo/registry 59 | ~/.cargo/git 60 | key: rust_${{ matrix.toolchain }}-cargo-${{ hashFiles('**/Cargo.toml') }} 61 | 62 | - name: Install diesel 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: install 66 | args: diesel_cli --no-default-features --features postgres 67 | 68 | - name: Run migrations 69 | run: | 70 | diesel setup 71 | diesel migration run 72 | 73 | - name: Run cargo fmt 74 | uses: actions-rs/cargo@v1 75 | if: matrix.toolchain == 'nightly' # NOTE: We run in nightly because `cargo fmt` in stable doesn't support ignoring certain files, including @generated tagged ones. 76 | with: 77 | command: fmt 78 | args: --all --check -- --config format_generated_files=false 79 | 80 | - name: Run tests 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: test 84 | args: --all-features --all-targets --verbose 85 | 86 | - name: Run doc tests 87 | uses: actions-rs/cargo@v1 88 | with: 89 | command: test 90 | args: --doc --verbose 91 | 92 | - name: Run clippy 93 | uses: actions-rs/cargo@v1 94 | with: 95 | command: clippy 96 | args: --all-targets --all-features -- -Dunused_imports 97 | -------------------------------------------------------------------------------- /src/middleware/verify_headers.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | dev::{Service, ServiceRequest, ServiceResponse, Transform}, 3 | error::ParseError, 4 | http, 5 | }; 6 | use futures::future::{ok, Either, Ready}; 7 | use futures::task::{Context, Poll}; 8 | 9 | pub struct VerifyAcceptHeader; 10 | 11 | const VALID_ACCEPT_HEADERS: [&str; 3] = [ 12 | "application/vnd.schemaregistry+json", 13 | "application/vnd.schemaregistry.v1+json", 14 | "application/json", 15 | ]; 16 | 17 | impl VerifyAcceptHeader { 18 | fn is_valid(headers: &http::header::HeaderMap) -> bool { 19 | match headers.get(http::header::ACCEPT) { 20 | Some(v) => match v.to_str() { 21 | Ok(s) => VALID_ACCEPT_HEADERS.iter().any(|h| *h == s), 22 | _ => false, 23 | }, 24 | None => false, 25 | } 26 | } 27 | } 28 | 29 | impl Transform for VerifyAcceptHeader 30 | where 31 | S: Service, 32 | S::Future: 'static, 33 | { 34 | type Response = ServiceResponse; 35 | type Error = S::Error; 36 | type InitError = (); 37 | type Transform = VerifyAcceptHeaderMiddleware; 38 | type Future = Ready>; 39 | 40 | fn new_transform(&self, service: S) -> Self::Future { 41 | ok(VerifyAcceptHeaderMiddleware { service }) 42 | } 43 | } 44 | 45 | pub struct VerifyAcceptHeaderMiddleware { 46 | service: S, 47 | } 48 | 49 | impl Service for VerifyAcceptHeaderMiddleware 50 | where 51 | S: Service, 52 | S::Future: 'static, 53 | { 54 | type Response = ServiceResponse; 55 | type Error = S::Error; 56 | type Future = Either>, S::Future>; 57 | 58 | fn poll_ready(&self, ct: &mut Context<'_>) -> Poll> { 59 | self.service.poll_ready(ct) 60 | } 61 | 62 | fn call(&self, req: ServiceRequest) -> Self::Future { 63 | if VerifyAcceptHeader::is_valid(req.headers()) { 64 | return Either::Right(self.service.call(req)); 65 | } 66 | Either::Left(ok(req.error_response(ParseError::Header))) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::VerifyAcceptHeader; 73 | use actix_web::http::header::{self, HeaderMap, HeaderValue}; 74 | 75 | #[test] 76 | fn middleware_accept_header_is_invalid() { 77 | let mut hm = HeaderMap::new(); 78 | hm.insert(header::ACCEPT, HeaderValue::from_static("invalid")); 79 | assert!(!VerifyAcceptHeader::is_valid(&hm)); 80 | } 81 | 82 | #[test] 83 | fn middleware_accept_header_missing() { 84 | let hm = HeaderMap::new(); 85 | assert!(!VerifyAcceptHeader::is_valid(&hm)); 86 | } 87 | 88 | #[test] 89 | fn middleware_accept_header_is_valid() { 90 | let mut hm = HeaderMap::new(); 91 | hm.insert(header::ACCEPT, HeaderValue::from_static("application/json")); 92 | assert!(VerifyAcceptHeader::is_valid(&hm)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/api/schemas.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | web::{Data, Json, Path}, 3 | HttpResponse, Responder, 4 | }; 5 | use log::info; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 9 | use crate::db::models::{ 10 | DeleteSchemaVersion, RegisterSchema, RegisterSchemaResponse, Schema, SchemaResponse, 11 | SchemaVersion, 12 | }; 13 | use crate::db::{DbManage, DbPool}; 14 | 15 | #[derive(Serialize, Deserialize, Debug)] 16 | pub struct SchemaBody { 17 | pub schema: String, 18 | } 19 | 20 | pub async fn get_schema(id: Path, db: Data) -> impl Responder { 21 | info!("method=get,id={}", id); 22 | 23 | let mut conn = db.connection()?; 24 | match Schema::get_by_id(&mut conn, id.into_inner()).map(|schema| SchemaResponse { 25 | schema: schema.json, 26 | }) { 27 | Ok(response) => Ok(HttpResponse::Ok().json(response)), 28 | Err(e) => Err(e), 29 | } 30 | } 31 | 32 | pub async fn delete_schema_version(info: Path<(String, u32)>, db: Data) -> impl Responder { 33 | let q = info.into_inner(); 34 | 35 | use crate::api::version::VersionLimit; 36 | 37 | let delete_schema_version = DeleteSchemaVersion { 38 | subject: q.0, 39 | version: q.1, 40 | }; 41 | let mut conn = db.connection()?; 42 | if !delete_schema_version.version.within_limits() { 43 | return Err(ApiError::new(ApiAvroErrorCode::InvalidVersion)); 44 | } 45 | match SchemaVersion::delete_version_with_subject(&mut conn, delete_schema_version) { 46 | Ok(r) => Ok(HttpResponse::Ok().body(format!("{}", r))), 47 | Err(e) => Err(e), 48 | } 49 | } 50 | 51 | pub async fn delete_schema_version_latest( 52 | subject: Path, 53 | db: Data, 54 | ) -> impl Responder { 55 | let subject = subject.into_inner(); 56 | 57 | use crate::api::version::VersionLimit; 58 | let mut conn = db.connection()?; 59 | 60 | let sv_response = 61 | crate::api::subjects::get_subject_version_from_db(&mut conn, subject.clone(), None)?; 62 | 63 | let delete_schema_version = DeleteSchemaVersion { 64 | subject, 65 | version: sv_response.version as u32, 66 | }; 67 | if !delete_schema_version.version.within_limits() { 68 | return Err(ApiError::new(ApiAvroErrorCode::InvalidVersion)); 69 | } 70 | match SchemaVersion::delete_version_with_subject(&mut conn, delete_schema_version) { 71 | Ok(r) => Ok(HttpResponse::Ok().body(format!("{}", r))), 72 | Err(e) => Err(e), 73 | } 74 | } 75 | 76 | pub async fn register_schema( 77 | subject: Path, 78 | body: Json, 79 | db: Data, 80 | ) -> impl Responder { 81 | let mut conn = db.connection()?; 82 | let new_schema = RegisterSchema { 83 | subject: subject.to_owned(), 84 | schema: body.into_inner().schema, 85 | }; 86 | match Schema::register_new_version(&mut conn, new_schema).map(|schema| RegisterSchemaResponse { 87 | id: format!("{}", schema.id), 88 | }) { 89 | Ok(response) => Ok(HttpResponse::Ok().json(response)), 90 | Err(e) => Err(e), 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use actix_web::web; 4 | 5 | use crate::api; 6 | use crate::health; 7 | use crate::middleware; 8 | 9 | pub fn monitoring_routing(cfg: &mut web::ServiceConfig) { 10 | cfg.service( 11 | web::scope("_") 12 | .service(web::resource("/health_check").route(web::get().to(health::status))), 13 | ); 14 | } 15 | 16 | pub fn api_routing(cfg: &mut web::ServiceConfig) { 17 | let password = 18 | env::var("SCHEMA_REGISTRY_PASSWORD").expect("Must pass a schema registry password"); 19 | 20 | cfg.service( 21 | web::scope("") 22 | .wrap(middleware::VerifyAcceptHeader) 23 | .wrap(middleware::VerifyAuthorization::new(&password)) 24 | .service( 25 | web::resource("/compatibility/subjects/{subject}/versions/{version}") 26 | .route(web::post().to(api::check_compatibility)), 27 | ) 28 | .service( 29 | web::resource("/config") 30 | .route(web::get().to(api::get_config)) 31 | .route(web::put().to(api::put_config)), 32 | ) 33 | .service( 34 | web::resource("/config/{subject}") 35 | .route(web::get().to(api::get_subject_config)) 36 | .route(web::put().to(api::put_subject_config)), 37 | ) 38 | .service(web::resource("/schemas/ids/{id}").route(web::get().to(api::get_schema))) 39 | .service( 40 | web::scope("/subjects") 41 | .service(web::resource("").to(api::get_subjects)) 42 | .service( 43 | web::resource("/{subject}") 44 | .route(web::post().to(api::post_subject)) 45 | .route(web::delete().to(api::delete_subject)), 46 | ) 47 | .service( 48 | web::resource("/{subject}/versions") 49 | .route(web::get().to(api::get_subject_versions)) 50 | .route(web::post().to(api::register_schema)), 51 | ) 52 | .service( 53 | web::resource("/{subject}/versions/latest") 54 | .route(web::get().to(api::get_subject_version_latest)) 55 | .route(web::delete().to(api::delete_schema_version_latest)), 56 | ) 57 | .service( 58 | web::resource("/{subject}/versions/{version}") 59 | .route(web::get().to(api::get_subject_version)) 60 | .route(web::delete().to(api::delete_schema_version)), 61 | ) 62 | .service( 63 | web::resource("/{subject}/versions/latest/schema") 64 | .to(api::get_subject_version_latest_schema), 65 | ) 66 | .service( 67 | web::resource("/{subject}/versions/{version}/schema") 68 | .to(api::get_subject_version_schema), 69 | ), 70 | ), 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /tests/common/server.rs: -------------------------------------------------------------------------------- 1 | use actix_test as test; 2 | use actix_web::{ 3 | error::PayloadError, 4 | http, 5 | web::{Bytes, Data}, 6 | App, 7 | }; 8 | use awc::{ClientRequest, ClientResponse}; 9 | use futures::{executor::block_on, stream::Stream}; 10 | use serde_json::Value as JsonValue; 11 | 12 | use super::settings::get_schema_registry_password; 13 | use crate::db::DbAuxOperations; 14 | use avro_schema_registry::app; 15 | use avro_schema_registry::db::{DbConnection, DbManage, DbPool}; 16 | 17 | pub struct ApiTesterServer(test::TestServer); 18 | 19 | pub fn setup() -> (ApiTesterServer, DbConnection) { 20 | let server = ApiTesterServer::new(); 21 | let mut conn = DbPool::new_pool(Some(1)).connection().unwrap(); 22 | conn.reset(); 23 | 24 | (server, conn) 25 | } 26 | 27 | impl ApiTesterServer { 28 | pub fn new() -> Self { 29 | Self(test::start(|| { 30 | App::new() 31 | .configure(app::monitoring_routing) 32 | .app_data(Data::new(DbPool::new_pool(Some(1)))) 33 | .configure(app::api_routing) 34 | })) 35 | } 36 | 37 | pub async fn test( 38 | &self, 39 | method: http::Method, 40 | path: &str, 41 | request_body: Option, 42 | expected_status: http::StatusCode, 43 | expected_body: &str, 44 | ) { 45 | let Self(server) = self; 46 | let req = server.request(method, server.url(path)).avro_headers(); 47 | 48 | match request_body { 49 | Some(b) => req 50 | .send_json(&b) 51 | .await 52 | .unwrap() 53 | .validate(expected_status, expected_body), 54 | 55 | None => req 56 | .send() 57 | .await 58 | .unwrap() 59 | .validate(expected_status, expected_body), 60 | }; 61 | } 62 | } 63 | 64 | trait AvroRequest { 65 | fn avro_headers(self) -> ClientRequest; 66 | } 67 | 68 | impl AvroRequest for ClientRequest { 69 | fn avro_headers(self) -> Self { 70 | self.insert_header((http::header::CONTENT_TYPE, "application/json")) 71 | .insert_header((http::header::ACCEPT, "application/vnd.schemaregistry+json")) 72 | .basic_auth("", get_schema_registry_password()) 73 | } 74 | } 75 | 76 | trait ValidateResponse { 77 | fn validate(self, expected_status: http::StatusCode, expected_body: &str); 78 | } 79 | 80 | impl ValidateResponse for ClientResponse 81 | where 82 | S: Stream> + Unpin, 83 | { 84 | fn validate(mut self, expected_status: http::StatusCode, expected_body_regex: &str) { 85 | assert_eq!(self.status(), expected_status); 86 | let b = block_on(self.body()).unwrap(); 87 | let s = b 88 | .iter() 89 | .map(|&c| c as char) 90 | .collect::() 91 | .replace("\\r\\n", "") 92 | .replace("\\n", ""); 93 | 94 | match regex::Regex::new(expected_body_regex) { 95 | Ok(re) => { 96 | assert!( 97 | re.is_match(&s), 98 | "{}", 99 | format!("body doesn't match regex: {s} != {expected_body_regex}") 100 | ) 101 | } 102 | Err(e) => panic!("{:?}", e), 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/db/models/subjects.rs: -------------------------------------------------------------------------------- 1 | use chrono::NaiveDateTime; 2 | use diesel::prelude::*; 3 | use serde::Serialize; 4 | 5 | use super::schema::*; 6 | 7 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 8 | 9 | #[derive(Debug, Identifiable, Queryable, Serialize)] 10 | #[diesel(table_name = subjects)] 11 | pub struct Subject { 12 | pub id: i64, 13 | pub name: String, 14 | pub created_at: NaiveDateTime, 15 | pub updated_at: NaiveDateTime, 16 | } 17 | 18 | impl Subject { 19 | /// Insert a new subject but ignore if it already exists. 20 | /// 21 | /// *Note:* 'ignore' in the case above means we will update the name if it already 22 | /// exists. This spares us complicated code to fetch, verify and then insert. 23 | pub fn insert(conn: &mut PgConnection, subject: String) -> Result { 24 | use super::schema::subjects::dsl::*; 25 | 26 | diesel::insert_into(subjects) 27 | .values(( 28 | name.eq(&subject), 29 | created_at.eq(diesel::dsl::now), 30 | updated_at.eq(diesel::dsl::now), 31 | )) 32 | .on_conflict(name) 33 | .do_update() 34 | .set(name.eq(&subject)) 35 | .get_result::(conn) 36 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 37 | } 38 | 39 | pub fn distinct_names(conn: &mut PgConnection) -> Result, ApiError> { 40 | use super::schema::subjects::dsl::{name, subjects}; 41 | 42 | subjects 43 | .select(name) 44 | .load::(conn) 45 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 46 | } 47 | 48 | pub fn get_by_name(conn: &mut PgConnection, subject: String) -> Result { 49 | use super::schema::subjects::dsl::{name, subjects}; 50 | match subjects.filter(name.eq(subject)).first::(conn) { 51 | Ok(s) => Ok(s), 52 | Err(diesel::result::Error::NotFound) => { 53 | Err(ApiError::new(ApiAvroErrorCode::SubjectNotFound)) 54 | } 55 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 56 | } 57 | } 58 | 59 | pub fn delete_by_name( 60 | conn: &mut PgConnection, 61 | subject_name: String, 62 | ) -> Result>, ApiError> { 63 | use super::SchemaVersion; 64 | 65 | match SchemaVersion::delete_subject_with_name(conn, subject_name) { 66 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 67 | Ok(res) => { 68 | if !res.is_empty() { 69 | Ok(res) 70 | } else { 71 | Err(ApiError::new(ApiAvroErrorCode::SubjectNotFound)) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | #[derive(Debug, Serialize)] 79 | pub struct SubjectList { 80 | pub content: Vec, 81 | } 82 | 83 | pub struct GetSubjects; 84 | 85 | #[derive(Debug, Serialize)] 86 | pub struct SubjectVersionsResponse { 87 | // TODO: this should be a new type with values between 1 and 2^31-1 88 | pub versions: Vec>, 89 | } 90 | 91 | pub struct GetSubjectVersions { 92 | pub subject: String, 93 | } 94 | 95 | #[derive(Debug, Serialize)] 96 | pub struct DeleteSubjectResponse { 97 | pub versions: Vec>, 98 | } 99 | 100 | pub struct DeleteSubject { 101 | pub subject: String, 102 | } 103 | 104 | #[derive(Debug, Serialize)] 105 | pub struct GetSubjectVersion { 106 | pub subject: String, 107 | pub version: Option, 108 | } 109 | 110 | #[derive(Debug, Serialize)] 111 | pub struct GetSubjectVersionResponse { 112 | pub subject: String, 113 | // TODO: The documentation mentions this but their example response doesn't include it 114 | pub id: i64, 115 | pub version: i32, 116 | pub schema: String, 117 | } 118 | -------------------------------------------------------------------------------- /tests/config.rs: -------------------------------------------------------------------------------- 1 | use actix_web::http; 2 | 3 | use crate::common::server::setup; 4 | use crate::db::DbAuxOperations; 5 | 6 | #[actix_rt::test] 7 | async fn test_get_global_config() { 8 | let (server, _) = setup(); 9 | 10 | // returns compatibility 11 | server 12 | .test( 13 | http::Method::GET, 14 | "/config", 15 | None, 16 | http::StatusCode::OK, 17 | r#"\{"compatibility":"BACKWARD"\}"#, 18 | ) 19 | .await; 20 | } 21 | 22 | #[actix_rt::test] 23 | async fn test_set_global_config_with_valid_compatibility_full() { 24 | let (server, _) = setup(); 25 | 26 | // returns compatibility 27 | server 28 | .test( 29 | http::Method::PUT, 30 | "/config", 31 | Some(json!({"compatibility": "FULL"})), 32 | http::StatusCode::OK, 33 | r#"\{"compatibility":"FULL"\}"#, 34 | ) 35 | .await; 36 | } 37 | 38 | #[actix_rt::test] 39 | async fn test_set_global_config_with_invalid_compatibility() { 40 | let (server, _) = setup(); 41 | // returns 422 with Invalid compatibility level 42 | server 43 | .test( 44 | http::Method::PUT, 45 | "/config", 46 | Some(json!({"compatibility": "NOT_VALID"})), 47 | http::StatusCode::UNPROCESSABLE_ENTITY, 48 | r#"\{"error_code":42203,"message":"Invalid compatibility level"\}"#, 49 | ) 50 | .await; 51 | } 52 | 53 | #[actix_rt::test] 54 | async fn test_get_compatibility_level_with_existent_subject() { 55 | let (server, mut conn) = setup(); 56 | conn.create_test_subject_with_config("FULL"); 57 | 58 | // returns valid compatibility 59 | server 60 | .test( 61 | http::Method::GET, 62 | "/config/test.subject", 63 | None, 64 | http::StatusCode::OK, 65 | r#"\{"compatibility":"FULL"\}"#, 66 | ) 67 | .await; 68 | } 69 | 70 | #[actix_rt::test] 71 | async fn test_get_compatibility_level_with_non_existent_subject() { 72 | let (server, mut conn) = setup(); 73 | conn.create_test_subject_with_config("FULL"); 74 | conn.reset_subjects(); 75 | 76 | // returns 404 with Invalid compatibility level 77 | server 78 | .test( 79 | http::Method::GET, 80 | "/config/test.subject", 81 | None, 82 | http::StatusCode::NOT_FOUND, 83 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 84 | ) 85 | .await; 86 | } 87 | 88 | #[actix_rt::test] 89 | async fn test_update_compatibility_level_with_existent_subject() { 90 | let (server, mut conn) = setup(); 91 | conn.create_test_subject_with_config("FULL"); 92 | 93 | // with valid compatibility FORWARD_TRANSITIVE it returns FORWARD_TRANSITIVE 94 | server 95 | .test( 96 | http::Method::PUT, 97 | "/config/test.subject", 98 | Some(json!({"compatibility": "FORWARD_TRANSITIVE"})), 99 | http::StatusCode::OK, 100 | r#"\{"compatibility":"FORWARD_TRANSITIVE"\}"#, 101 | ) 102 | .await; 103 | 104 | // with invalid compatibility it returns 422 105 | server 106 | .test( 107 | http::Method::PUT, 108 | "/config/test.subject", 109 | Some(json!({"compatibility": "NOT_VALID"})), 110 | http::StatusCode::UNPROCESSABLE_ENTITY, 111 | r#"\{"error_code":42203,"message":"Invalid compatibility level"}"#, 112 | ) 113 | .await; 114 | } 115 | 116 | #[actix_rt::test] 117 | async fn test_update_compatibility_level_with_non_existent_subject() { 118 | let (server, mut conn) = setup(); 119 | conn.reset_subjects(); 120 | 121 | // with valid compatibility FULL it returns 404 122 | server 123 | .test( 124 | http::Method::PUT, 125 | "/config/test.subject", 126 | Some(json!({"compatibility": "FULL"})), 127 | http::StatusCode::NOT_FOUND, 128 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 129 | ) 130 | .await; 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Avro Schema Registry 2 | 3 | [![Rust](https://github.com/nlopes/avro-schema-registry/actions/workflows/ci.yml/badge.svg)](https://github.com/nlopes/avro-schema-registry/actions/workflows/ci.yml) 4 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nlopes/avro-schema-registry/blob/master/LICENSE) 5 | 6 | **DO NOT USE IN PRODUCTION** 7 | 8 | This is an implementation of the Confluent Schema Registry API (mostly) compatible with 9 | the Rails implementation by 10 | [salsify/avro-schema-registry](https://github.com/salsify/avro-schema-registry). 11 | 12 | I kept the database schema used by `salsify` and my main goal was to replace the Rails 13 | service with this one. There's nothing wrong with theirs, I just happen to enjoy Rust more 14 | than Ruby. All thanks should go to `salsify` for their initial design and ideas on using 15 | PostgreSQL instead of Kafka as a backend. 16 | 17 | Note: the backend used is PostgreSQL (schema compatible with salsify/avro-schema-registry 18 | PostgreSQL schema). The Confluent Schema Registry uses Kafka as a backend therefore we 19 | don't provide exactly the same semantics (but close enough). 20 | 21 | ## Fingerprint 22 | 23 | In [salsify/avro-schema-registry](https://github.com/salsify/avro-schema-registry) two 24 | fingerprints are present (v1 and v2). In this implementation, we only make use of v2 and 25 | do NOT support v1. 26 | 27 | If you are still using fingerprints with v1, please make sure you migrate first, before 28 | using this service as your API. 29 | 30 | ## Endpoints 31 | 32 | | Endpoint | Method | Maturity | 33 | |---|---|---| 34 | | `/compatibility/subjects/{subject}/versions/{version}` | POST | Ready (NONE, BACKWARD, FORWARD, FULL); Unimplemented (TRANSITIVE) | 35 | | `/config` | GET | Ready | 36 | | `/config` | PUT | Ready | 37 | | `/config/{subject}` | GET | Ready | 38 | | `/config/{subject}` | PUT | Ready | 39 | | `/schemas/ids/{id}`| GET | Ready | 40 | | `/subjects` | GET | Ready | 41 | | `/subjects/{subject}` | DELETE | Ready | 42 | | `/subjects/{subject}` | POST | Ready | 43 | | `/subjects/{subject}/versions` | GET | Ready | 44 | | `/subjects/{subject}/versions` | POST | Ready | 45 | | `/subjects/{subject}/versions/latest` | DELETE | Ready | 46 | | `/subjects/{subject}/versions/latest` | GET | Ready | 47 | | `/subjects/{subject}/versions/{version}` | DELETE | Ready | 48 | | `/subjects/{subject}/versions/{version}` | GET | Ready | 49 | | `/subjects/{subject}/versions/latest/schema` | GET | Ready | 50 | | `/subjects/{subject}/versions/{version}/schema` | GET | Ready | 51 | 52 | ## Extra Endpoints 53 | 54 | | Endpoint | Method | Maturity | 55 | |---|---|---| 56 | | `/_/health_check` | GET | Incomplete | 57 | | `/_/metrics` | GET | Ready | 58 | 59 | 60 | ## Build 61 | 62 | ``` 63 | cargo build --release 64 | ``` 65 | 66 | ## Run 67 | 68 | This assumes you have a running PostgreSQL instance (versions 9.5 and above) and 69 | you've run the diesel setup (with its migrations). 70 | 71 | 1) Setup env (everything is controlled through environment variables) 72 | ``` 73 | export SENTRY_URL="http://sentry-url/id" \ # optional 74 | DEFAULT_HOST=127.0.0.1:8080 \ # optional (default is 127.0.0.1:8080) 75 | DATABASE_URL=postgres://postgres:@localhost:5432/diesel_testing \ 76 | SCHEMA_REGISTRY_PASSWORD=silly_password 77 | ``` 78 | 79 | 2) Run application 80 | ``` 81 | # If you haven't set PORT, it listens on the default 8080 82 | cargo run # or the binary after running `cargo build` 83 | ``` 84 | 85 | 3) Make a request 86 | ``` 87 | # If you assume the credentials above: 88 | curl -v -H 'Authorization: Basic OnNpbGx5X3Bhc3N3b3Jk' -H 'Accept: application/json' http://127.0.0.1:8080/config 89 | ``` 90 | 91 | ## Tests 92 | 93 | ### Unit 94 | 95 | ``` 96 | cargo test middleware 97 | ``` 98 | 99 | ### Integration 100 | 101 | 1) Setup testing environment variables 102 | ``` 103 | export RUST_TEST_THREADS=1 \ 104 | DATABASE_URL=postgres://postgres:@localhost:5432/diesel_testing \ 105 | SCHEMA_REGISTRY_PASSWORD=silly_password 106 | ``` 107 | 108 | 2) Run test suite 109 | ``` 110 | cargo test speculate 111 | ``` 112 | 113 | ## Important 114 | 115 | We don't ever return `Error code 50003 -- Error while forwarding the request to the 116 | master`. This is because this error is specific to Kafka. 117 | 118 | ## Contributing 119 | 120 | You are more than welcome to contribute to this project. Fork and make a Pull Request, or 121 | create an Issue if you see any problem. 122 | -------------------------------------------------------------------------------- /tests/db/mod.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | 3 | use avro_schema_registry::db::models::Schema; 4 | use avro_schema_registry::db::DbConnection; 5 | 6 | pub trait DbAuxOperations { 7 | fn reset(&mut self); 8 | fn reset_subjects(&mut self); 9 | 10 | // These two are not yet used anywhere but I wanted them as part of the trait and 11 | // implemented 12 | #[allow(dead_code)] 13 | fn reset_schemas(&mut self); 14 | #[allow(dead_code)] 15 | fn reset_configs_global(&mut self); 16 | 17 | fn create_test_subject_with_config(&mut self, compat: &str); 18 | fn add_subjects(&mut self, subjects: Vec); 19 | fn register_schema(&mut self, subject: String, schema: String) -> Schema; 20 | } 21 | 22 | impl DbAuxOperations for DbConnection { 23 | fn reset(&mut self) { 24 | use avro_schema_registry::db::models::schema::configs::dsl::configs; 25 | use avro_schema_registry::db::models::schema::schema_versions::dsl::schema_versions; 26 | use avro_schema_registry::db::models::schema::schemas::dsl::schemas; 27 | use avro_schema_registry::db::models::schema::subjects::dsl::subjects; 28 | 29 | self.transaction::<_, diesel::result::Error, _>(|conn| { 30 | diesel::delete(configs).execute(conn)?; 31 | diesel::delete(schemas).execute(conn)?; 32 | diesel::delete(subjects).execute(conn)?; 33 | diesel::delete(schema_versions).execute(conn) 34 | }) 35 | .unwrap(); 36 | } 37 | 38 | #[allow(dead_code)] 39 | fn reset_schemas(&mut self) { 40 | use avro_schema_registry::db::models::schema::schemas::dsl::*; 41 | diesel::delete(schemas).execute(self).unwrap(); 42 | } 43 | 44 | fn reset_subjects(&mut self) { 45 | use avro_schema_registry::db::models::schema::subjects::dsl::*; 46 | diesel::delete(subjects).execute(self).unwrap(); 47 | } 48 | 49 | #[allow(dead_code)] 50 | fn reset_configs_global(&mut self) { 51 | use avro_schema_registry::db::models::schema::configs::dsl::*; 52 | 53 | self.transaction::<_, diesel::result::Error, _>(|conn| { 54 | diesel::update(configs) 55 | .filter(id.eq(0)) 56 | .set(compatibility.eq("BACKWARD")) 57 | .execute(conn) 58 | }) 59 | .unwrap(); 60 | } 61 | 62 | fn create_test_subject_with_config(&mut self, compat: &str) { 63 | use avro_schema_registry::db::models::schema::configs::dsl::{ 64 | compatibility, configs, created_at as config_created_at, subject_id, 65 | updated_at as config_updated_at, 66 | }; 67 | use avro_schema_registry::db::models::schema::subjects::dsl::*; 68 | use avro_schema_registry::db::models::Subject; 69 | 70 | self.transaction::<_, diesel::result::Error, _>(|conn| { 71 | diesel::insert_into(subjects) 72 | .values(( 73 | name.eq("test.subject"), 74 | created_at.eq(diesel::dsl::now), 75 | updated_at.eq(diesel::dsl::now), 76 | )) 77 | .get_result::(conn) 78 | .and_then(|subject| { 79 | diesel::insert_into(configs) 80 | .values(( 81 | compatibility.eq(compat), 82 | config_created_at.eq(diesel::dsl::now), 83 | config_updated_at.eq(diesel::dsl::now), 84 | subject_id.eq(subject.id), 85 | )) 86 | .execute(conn) 87 | }) 88 | }) 89 | .unwrap(); 90 | } 91 | 92 | fn add_subjects(&mut self, subjects: Vec) { 93 | use avro_schema_registry::api::errors::ApiError; 94 | use avro_schema_registry::db::models::Subject; 95 | 96 | self.transaction::(|conn| { 97 | Ok(subjects 98 | .into_iter() 99 | .map(|subject| { 100 | Subject::insert(conn, subject) 101 | .expect("could not insert subject") 102 | .name 103 | }) 104 | .collect()) 105 | }) 106 | .unwrap(); 107 | } 108 | 109 | fn register_schema(&mut self, subject: String, schema: String) -> Schema { 110 | use avro_schema_registry::db::models::RegisterSchema; 111 | Schema::register_new_version(self, RegisterSchema { subject, schema }).unwrap() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/api/subjects.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | web::{Data, Json, Path}, 3 | HttpResponse, Responder, 4 | }; 5 | 6 | use crate::api::{ 7 | errors::{ApiAvroErrorCode, ApiError}, 8 | SchemaBody, 9 | }; 10 | use crate::db::models::{ 11 | DeleteSubjectResponse, GetSubjectVersionResponse, Schema, SchemaResponse, SchemaVersion, 12 | Subject, SubjectList, SubjectVersionsResponse, 13 | }; 14 | use crate::db::{DbManage, DbPool}; 15 | 16 | pub async fn get_subjects(db: Data) -> impl Responder { 17 | let mut conn = db.connection()?; 18 | match Subject::distinct_names(&mut conn).map(|content| SubjectList { content }) { 19 | Ok(subjects) => Ok(HttpResponse::Ok().json(subjects.content)), 20 | Err(e) => Err(e), 21 | } 22 | } 23 | 24 | pub async fn get_subject_versions(subject: Path, db: Data) -> impl Responder { 25 | //subject.into_inner() 26 | let mut conn = db.connection()?; 27 | match SchemaVersion::versions_with_subject_name(&mut conn, subject.into_inner()) 28 | .map(|versions| SubjectVersionsResponse { versions }) 29 | { 30 | Ok(r) => Ok(HttpResponse::Ok().json(r.versions)), 31 | Err(e) => Err(e), 32 | } 33 | } 34 | 35 | pub async fn delete_subject(subject: Path, db: Data) -> impl Responder { 36 | let mut conn = db.connection()?; 37 | match Subject::delete_by_name(&mut conn, subject.into_inner()) 38 | .map(|versions| DeleteSubjectResponse { versions }) 39 | { 40 | Ok(r) => Ok(HttpResponse::Ok().json(r.versions)), 41 | Err(e) => Err(e), 42 | } 43 | } 44 | 45 | /// `get_subject_version_from_db` fetches a specific subject version pair from the 46 | /// database, given a subject name and an optional version. If the version is not given, 47 | /// then we get the latest schema id. 48 | pub fn get_subject_version_from_db( 49 | conn: &mut diesel::r2d2::PooledConnection< 50 | diesel::r2d2::ConnectionManager, 51 | >, 52 | subject: String, 53 | version: Option, 54 | ) -> Result { 55 | use crate::api::version::VersionLimit; 56 | 57 | match version { 58 | Some(v) => { 59 | if !v.within_limits() { 60 | return Err(ApiError::new(ApiAvroErrorCode::InvalidVersion)); 61 | } 62 | SchemaVersion::get_schema_id(conn, subject.to_string(), v) 63 | } 64 | None => SchemaVersion::get_schema_id_from_latest(conn, subject.to_string()), 65 | } 66 | .map(|o| GetSubjectVersionResponse { 67 | subject: subject.to_string(), 68 | id: o.0, 69 | version: o.1, 70 | schema: o.2, 71 | }) 72 | } 73 | 74 | // TODO(nlopes): maybe new type here 75 | // 76 | // According to 77 | // https://docs.confluent.io/3.1.0/schema-registry/docs/api.html#get--subjects-(string-%20subject)-versions-(versionId-%20version) 78 | // the Version ID should be in the range of 1 to 2^31-1, which isn't u32. We should create 79 | // a new type with the boundaries of this. 80 | pub async fn get_subject_version(info: Path<(String, u32)>, db: Data) -> impl Responder { 81 | let q = info.into_inner(); 82 | 83 | let mut conn = db.connection()?; 84 | match get_subject_version_from_db(&mut conn, q.0, Some(q.1)) { 85 | Ok(r) => Ok(HttpResponse::Ok().json(r)), 86 | Err(e) => Err(e), 87 | } 88 | } 89 | 90 | pub async fn get_subject_version_latest(subject: Path, db: Data) -> impl Responder { 91 | let mut conn = db.connection()?; 92 | match get_subject_version_from_db(&mut conn, subject.into_inner(), None) { 93 | Ok(r) => Ok(HttpResponse::Ok().json(r)), 94 | Err(e) => Err(e), 95 | } 96 | } 97 | 98 | // TODO: for now, do the same as for `get_subject_version` and then extract only the 99 | // schema 100 | pub async fn get_subject_version_schema( 101 | info: Path<(String, u32)>, 102 | db: Data, 103 | ) -> impl Responder { 104 | let q = info.into_inner(); 105 | 106 | let mut conn = db.connection()?; 107 | match get_subject_version_from_db(&mut conn, q.0, Some(q.1)) { 108 | Ok(r) => Ok(HttpResponse::Ok().json(SchemaResponse { schema: r.schema })), 109 | Err(e) => Err(e), 110 | } 111 | } 112 | 113 | pub async fn get_subject_version_latest_schema( 114 | subject: Path, 115 | db: Data, 116 | ) -> impl Responder { 117 | let mut conn = db.connection()?; 118 | match get_subject_version_from_db(&mut conn, subject.into_inner(), None) { 119 | Ok(r) => Ok(HttpResponse::Ok().json(SchemaResponse { schema: r.schema })), 120 | Err(e) => Err(e), 121 | } 122 | } 123 | 124 | pub async fn post_subject( 125 | subject: Path, 126 | body: Json, 127 | db: Data, 128 | ) -> impl Responder { 129 | let mut conn = db.connection()?; 130 | match Schema::verify_registration(&mut conn, subject.into_inner(), body.into_inner().schema) { 131 | Ok(response) => Ok(HttpResponse::Ok().json(response)), 132 | Err(e) => Err(e), 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/api/errors.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{http::StatusCode, HttpResponse}; 2 | use serde::Serialize; 3 | 4 | // TODO: maybe replace this with serde_aux::serde_aux_enum_number_declare 5 | macro_rules! enum_number { 6 | ($name:ident { $($variant:ident = $value:expr, )* }) => { 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub enum $name { 9 | $($variant = $value,)* 10 | } 11 | 12 | impl ::serde::Serialize for $name { 13 | fn serialize(&self, serializer: S) -> Result 14 | where 15 | S: ::serde::Serializer, 16 | { 17 | serializer.serialize_u16(*self as u16) 18 | } 19 | } 20 | } 21 | } 22 | 23 | // We use the macro to ensure we serialize as numbers, not as the name. 24 | enum_number!(ApiAvroErrorCode { 25 | SubjectNotFound = 40401, 26 | VersionNotFound = 40402, 27 | SchemaNotFound = 40403, 28 | 29 | InvalidAvroSchema = 42201, 30 | InvalidVersion = 42202, 31 | InvalidCompatibilityLevel = 42203, 32 | 33 | BackendDatastoreError = 50001, 34 | OperationTimedOut = 50002, 35 | MasterForwardingError = 50003, 36 | }); 37 | 38 | impl ApiAvroErrorCode { 39 | pub const fn message(&self) -> &str { 40 | match self { 41 | Self::SubjectNotFound => "Subject not found", 42 | Self::VersionNotFound => "Version not found", 43 | Self::SchemaNotFound => "Schema not found", 44 | 45 | Self::InvalidAvroSchema => "Invalid Avro schema", 46 | Self::InvalidVersion => "Invalid version", 47 | Self::InvalidCompatibilityLevel => "Invalid compatibility level", 48 | 49 | Self::BackendDatastoreError => "Error in the backend datastore", 50 | Self::OperationTimedOut => "Operation timed out", 51 | Self::MasterForwardingError => "Error while forwarding the request to the master", 52 | } 53 | } 54 | } 55 | 56 | #[derive(Debug, Serialize, Clone)] 57 | pub struct ApiErrorResponse { 58 | pub error_code: ApiAvroErrorCode, 59 | pub message: String, 60 | } 61 | 62 | #[derive(Debug, thiserror::Error, Clone)] 63 | #[error("{}", response)] 64 | pub struct ApiError { 65 | pub status_code: StatusCode, 66 | pub response: ApiErrorResponse, 67 | } 68 | 69 | impl ApiError { 70 | pub fn new(error_code: ApiAvroErrorCode) -> Self { 71 | let status_code = match error_code { 72 | ApiAvroErrorCode::SubjectNotFound => StatusCode::NOT_FOUND, 73 | ApiAvroErrorCode::VersionNotFound => StatusCode::NOT_FOUND, 74 | ApiAvroErrorCode::SchemaNotFound => StatusCode::NOT_FOUND, 75 | 76 | ApiAvroErrorCode::InvalidAvroSchema => StatusCode::UNPROCESSABLE_ENTITY, 77 | ApiAvroErrorCode::InvalidVersion => StatusCode::UNPROCESSABLE_ENTITY, 78 | ApiAvroErrorCode::InvalidCompatibilityLevel => StatusCode::UNPROCESSABLE_ENTITY, 79 | 80 | ApiAvroErrorCode::BackendDatastoreError => StatusCode::INTERNAL_SERVER_ERROR, 81 | ApiAvroErrorCode::OperationTimedOut => StatusCode::INTERNAL_SERVER_ERROR, 82 | ApiAvroErrorCode::MasterForwardingError => StatusCode::INTERNAL_SERVER_ERROR, 83 | }; 84 | 85 | Self { 86 | status_code, 87 | response: ApiErrorResponse { 88 | error_code, 89 | message: error_code.message().to_string(), 90 | }, 91 | } 92 | } 93 | } 94 | 95 | impl std::fmt::Display for ApiAvroErrorCode { 96 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 97 | write!(f, "{}", &self.message()) 98 | } 99 | } 100 | 101 | impl std::fmt::Display for ApiErrorResponse { 102 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 103 | write!(f, "{}", serde_json::json!(self)) 104 | } 105 | } 106 | 107 | impl actix_web::ResponseError for ApiError { 108 | fn error_response(&self) -> HttpResponse { 109 | match self.status_code { 110 | StatusCode::NOT_FOUND => HttpResponse::NotFound().json(&self.response), 111 | StatusCode::INTERNAL_SERVER_ERROR => { 112 | HttpResponse::InternalServerError().json(&self.response) 113 | } 114 | StatusCode::UNPROCESSABLE_ENTITY => { 115 | HttpResponse::build(StatusCode::UNPROCESSABLE_ENTITY).json(&self.response) 116 | } 117 | _ => HttpResponse::NotImplemented().finish(), 118 | } 119 | } 120 | } 121 | 122 | impl std::error::Error for ApiAvroErrorCode { 123 | fn description(&self) -> &str { 124 | self.message() 125 | } 126 | 127 | fn cause(&self) -> Option<&dyn std::error::Error> { 128 | None 129 | } 130 | } 131 | 132 | impl std::convert::From for ApiAvroErrorCode { 133 | fn from(_error: diesel::result::Error) -> Self { 134 | Self::BackendDatastoreError 135 | } 136 | } 137 | 138 | impl std::convert::From for ApiError { 139 | fn from(_error: diesel::result::Error) -> Self { 140 | Self::new(ApiAvroErrorCode::BackendDatastoreError) 141 | } 142 | } 143 | 144 | impl std::convert::From for ApiError { 145 | fn from(_error: actix::MailboxError) -> Self { 146 | Self::new(ApiAvroErrorCode::BackendDatastoreError) 147 | } 148 | } 149 | 150 | impl std::convert::From> for ApiError { 151 | fn from(error: actix_threadpool::BlockingError) -> Self { 152 | match error { 153 | actix_threadpool::BlockingError::Canceled => { 154 | Self::new(ApiAvroErrorCode::BackendDatastoreError) 155 | } 156 | actix_threadpool::BlockingError::Error(e) => e, 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/middleware/verify_auth.rs: -------------------------------------------------------------------------------- 1 | use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; 2 | use actix_web::error::{Error, ErrorBadRequest, ErrorForbidden, ParseError}; 3 | use actix_web::http::header::HeaderMap; 4 | use base64::{engine::general_purpose::STANDARD as StandardEngine, Engine as _}; 5 | use futures::future::{ok, Either, Ready}; 6 | use futures::task::{Context, Poll}; 7 | 8 | pub struct VerifyAuthorization { 9 | password: String, 10 | } 11 | 12 | impl VerifyAuthorization { 13 | pub fn new(password: &str) -> Self { 14 | Self { 15 | password: password.to_string(), 16 | } 17 | } 18 | 19 | fn validate(headers: &HeaderMap, password: &str) -> Result<(), Error> { 20 | let authorization = headers 21 | .get("Authorization") 22 | .ok_or_else(|| ErrorBadRequest(ParseError::Header))? 23 | .to_str() 24 | .map_err(ErrorBadRequest)?; 25 | 26 | if authorization.len() < 7 { 27 | // 'Basic ' is 6 chars long, so anything below 7 is invalid 28 | return Err(ErrorBadRequest(ParseError::Header)); 29 | } 30 | 31 | let (basic, base64_auth) = authorization.split_at(6); 32 | if basic.ne("Basic ") { 33 | return Err(ErrorBadRequest(ParseError::Header)); 34 | } 35 | 36 | // Decode the base64 auth which will contain the username and password in the 37 | // format of username:password (we ignore the username) 38 | match StandardEngine.decode(base64_auth) { 39 | Ok(bytes) => { 40 | let mut basic_creds = std::str::from_utf8(&bytes)? 41 | .trim_end_matches('\n') 42 | .splitn(2, ':'); 43 | let _username = basic_creds 44 | .next() 45 | .ok_or_else(|| ErrorBadRequest(ParseError::Header))?; 46 | 47 | let header_password = basic_creds 48 | .next() 49 | .ok_or_else(|| ErrorBadRequest(ParseError::Header))?; 50 | 51 | if *header_password != *password { 52 | return Err(ErrorForbidden(ParseError::Header)); 53 | } 54 | Ok(()) 55 | } 56 | Err(_) => Err(ErrorBadRequest(ParseError::Header)), 57 | } 58 | } 59 | } 60 | 61 | impl Transform for VerifyAuthorization 62 | where 63 | S: Service, 64 | S::Future: 'static, 65 | { 66 | type Response = ServiceResponse; 67 | type Error = S::Error; 68 | type InitError = (); 69 | type Transform = VerifyAuthorizationMiddleware; 70 | type Future = Ready>; 71 | 72 | fn new_transform(&self, service: S) -> Self::Future { 73 | ok(VerifyAuthorizationMiddleware { 74 | service, 75 | password: self.password.clone(), 76 | }) 77 | } 78 | } 79 | 80 | pub struct VerifyAuthorizationMiddleware { 81 | service: S, 82 | password: String, 83 | } 84 | 85 | impl Service for VerifyAuthorizationMiddleware 86 | where 87 | S: Service, 88 | S::Future: 'static, 89 | { 90 | type Response = ServiceResponse; 91 | type Error = S::Error; 92 | type Future = Either>, S::Future>; 93 | 94 | fn poll_ready(&self, ct: &mut Context<'_>) -> Poll> { 95 | self.service.poll_ready(ct) 96 | } 97 | 98 | fn call(&self, req: ServiceRequest) -> Self::Future { 99 | match VerifyAuthorization::validate(req.headers(), &self.password) { 100 | Ok(_) => Either::Right(self.service.call(req)), 101 | Err(_) => Either::Left(ok(req.error_response(ParseError::Header))), 102 | } 103 | } 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::VerifyAuthorization; 109 | use actix_web::http::header::{self, HeaderMap, HeaderValue}; 110 | 111 | const VALID_PASSWORD: &str = "some_password"; 112 | const INVALID_PASSWORD: &str = "some_invalid_password"; 113 | const CORRECT_AUTH: &str = "Basic OnNvbWVfcGFzc3dvcmQK"; 114 | 115 | #[test] 116 | fn middleware_with_valid_password() { 117 | let mut headers = HeaderMap::new(); 118 | headers.insert( 119 | header::AUTHORIZATION, 120 | HeaderValue::from_static(CORRECT_AUTH), 121 | ); 122 | assert!(VerifyAuthorization::validate(&headers, VALID_PASSWORD).is_ok()); 123 | } 124 | 125 | #[test] 126 | fn middleware_with_invalid_password() { 127 | let mut headers = HeaderMap::new(); 128 | headers.insert( 129 | header::AUTHORIZATION, 130 | HeaderValue::from_static(CORRECT_AUTH), 131 | ); 132 | assert!(VerifyAuthorization::validate(&headers, INVALID_PASSWORD).is_err()); 133 | } 134 | 135 | #[test] 136 | fn middleware_with_malformed_header() { 137 | let headers = HeaderMap::new(); 138 | assert!(VerifyAuthorization::validate(&headers, VALID_PASSWORD).is_err()); 139 | } 140 | 141 | #[test] 142 | fn middleware_with_malformed_header_content() { 143 | let mut headers = HeaderMap::new(); 144 | headers.insert(header::AUTHORIZATION, HeaderValue::from_static("bad")); 145 | assert!(VerifyAuthorization::validate(&headers, VALID_PASSWORD).is_err()); 146 | } 147 | 148 | #[test] 149 | fn middleware_with_wrong_content_length() { 150 | let mut headers = HeaderMap::new(); 151 | headers.insert(header::AUTHORIZATION, HeaderValue::from_static("Basic ")); 152 | assert!(VerifyAuthorization::validate(&headers, VALID_PASSWORD).is_err()); 153 | } 154 | 155 | #[test] 156 | fn middleware_with_bad_base64() { 157 | let mut headers = HeaderMap::new(); 158 | headers.insert(header::AUTHORIZATION, HeaderValue::from_static("Basic meh")); 159 | assert!(VerifyAuthorization::validate(&headers, VALID_PASSWORD).is_err()); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/compatibility.rs: -------------------------------------------------------------------------------- 1 | use actix_web::http; 2 | 3 | use crate::common::server::setup; 4 | use crate::db::DbAuxOperations; 5 | use avro_schema_registry::api::SchemaBody; 6 | 7 | #[actix_rt::test] 8 | async fn test_schema_for_compatibility_with_non_existent_subject() { 9 | let (server, _) = setup(); 10 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 11 | let schema = SchemaBody { schema: schema_s }; 12 | 13 | server 14 | .test( 15 | http::Method::POST, 16 | "/compatibility/subjects/test.subject/versions/1", 17 | Some(json!(schema)), 18 | http::StatusCode::NOT_FOUND, 19 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 20 | ) 21 | .await; 22 | } 23 | 24 | #[actix_rt::test] 25 | async fn test_schema_for_compatibility_with_subject_and_with_non_existent_version() { 26 | let (server, mut conn) = setup(); 27 | conn.create_test_subject_with_config("FORWARD"); 28 | 29 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 30 | let schema = SchemaBody { schema: schema_s }; 31 | 32 | // it returns 404 with 'Version not found' 33 | server 34 | .test( 35 | http::Method::POST, 36 | "/compatibility/subjects/test.subject/versions/2", 37 | Some(json!(schema)), 38 | http::StatusCode::NOT_FOUND, 39 | r#"\{"error_code":40402,"message":"Version not found"\}"#, 40 | ) 41 | .await; 42 | } 43 | 44 | #[actix_rt::test] 45 | async fn test_schema_for_forward_compatibility_with_subject_and_with_version() { 46 | let (server, mut conn) = setup(); 47 | conn.create_test_subject_with_config("FORWARD"); 48 | 49 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 50 | let schema_forward_compatible_s = 51 | std::fs::read_to_string("tests/fixtures/schema_forward_compatible.json").unwrap(); 52 | 53 | let _ = conn.register_schema(String::from("test.subject"), schema_s.to_string()); 54 | let schema_forward_compatible = SchemaBody { 55 | schema: schema_forward_compatible_s.to_string(), 56 | }; 57 | 58 | server 59 | .test( 60 | http::Method::POST, 61 | "/compatibility/subjects/test.subject/versions/1", 62 | Some(json!(schema_forward_compatible)), 63 | http::StatusCode::OK, 64 | r#"\{"is_compatible":true\}"#, 65 | ) 66 | .await; 67 | 68 | // Should not be backwards compatible 69 | let schema_backward_compatible_s = 70 | std::fs::read_to_string("tests/fixtures/schema_backward_compatible.json").unwrap(); 71 | 72 | let schema_backward_compatible = SchemaBody { 73 | schema: schema_backward_compatible_s.to_string(), 74 | }; 75 | 76 | server 77 | .test( 78 | http::Method::POST, 79 | "/compatibility/subjects/test.subject/versions/1", 80 | Some(json!(schema_backward_compatible)), 81 | http::StatusCode::OK, 82 | r#"\{"is_compatible":false\}"#, 83 | ) 84 | .await; 85 | } 86 | 87 | #[actix_rt::test] 88 | async fn test_schema_for_backward_compatibility_with_subject_and_with_version() { 89 | let (server, mut conn) = setup(); 90 | conn.create_test_subject_with_config("BACKWARD"); 91 | 92 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 93 | let schema_backward_compatible_s = 94 | std::fs::read_to_string("tests/fixtures/schema_backward_compatible.json").unwrap(); 95 | 96 | let _ = conn.register_schema(String::from("test.subject"), schema_s.to_string()); 97 | let schema2 = SchemaBody { 98 | schema: schema_backward_compatible_s.to_string(), 99 | }; 100 | 101 | server 102 | .test( 103 | http::Method::POST, 104 | "/compatibility/subjects/test.subject/versions/1", 105 | Some(json!(schema2)), 106 | http::StatusCode::OK, 107 | r#"\{"is_compatible":true\}"#, 108 | ) 109 | .await; 110 | 111 | let schema_forward_compatible_s = 112 | std::fs::read_to_string("tests/fixtures/schema_forward_compatible.json").unwrap(); 113 | 114 | let schema_forward_compatible = SchemaBody { 115 | schema: schema_forward_compatible_s.to_string(), 116 | }; 117 | 118 | server 119 | .test( 120 | http::Method::POST, 121 | "/compatibility/subjects/test.subject/versions/1", 122 | Some(json!(schema_forward_compatible)), 123 | http::StatusCode::OK, 124 | r#"\{"is_compatible":false\}"#, 125 | ) 126 | .await; 127 | } 128 | 129 | #[actix_rt::test] 130 | async fn test_schema_for_full_compatibility_with_subject_and_with_version() { 131 | let (server, mut conn) = setup(); 132 | conn.create_test_subject_with_config("FULL"); 133 | 134 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 135 | let schema_backward_compatible_s = 136 | std::fs::read_to_string("tests/fixtures/schema_backward_compatible.json").unwrap(); 137 | 138 | let _ = conn.register_schema(String::from("test.subject"), schema_s.to_string()); 139 | let schema2 = SchemaBody { 140 | schema: schema_backward_compatible_s.to_string(), 141 | }; 142 | 143 | server 144 | .test( 145 | http::Method::POST, 146 | "/compatibility/subjects/test.subject/versions/1", 147 | Some(json!(schema2)), 148 | http::StatusCode::OK, 149 | r#"\{"is_compatible":false\}"#, 150 | ) 151 | .await; 152 | 153 | let schema_forward_compatible_s = 154 | std::fs::read_to_string("tests/fixtures/schema_forward_compatible.json").unwrap(); 155 | 156 | let schema_forward_compatible = SchemaBody { 157 | schema: schema_forward_compatible_s.to_string(), 158 | }; 159 | 160 | server 161 | .test( 162 | http::Method::POST, 163 | "/compatibility/subjects/test.subject/versions/1", 164 | Some(json!(schema_forward_compatible)), 165 | http::StatusCode::OK, 166 | r#"\{"is_compatible":false\}"#, 167 | ) 168 | .await; 169 | 170 | let schema_full_compatible_s = 171 | std::fs::read_to_string("tests/fixtures/schema_full_compatible.json").unwrap(); 172 | 173 | let schema_full_compatible = SchemaBody { 174 | schema: schema_full_compatible_s.to_string(), 175 | }; 176 | 177 | server 178 | .test( 179 | http::Method::POST, 180 | "/compatibility/subjects/test.subject/versions/1", 181 | Some(json!(schema_full_compatible)), 182 | http::StatusCode::OK, 183 | r#"\{"is_compatible":true\}"#, 184 | ) 185 | .await; 186 | } 187 | -------------------------------------------------------------------------------- /src/db/models/schemas.rs: -------------------------------------------------------------------------------- 1 | use avro_rs::schema_compatibility::SchemaCompatibility; 2 | use chrono::{NaiveDateTime, Utc}; 3 | use diesel::prelude::*; 4 | use serde::Serialize; 5 | 6 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 7 | 8 | use super::schema::*; 9 | use super::{GetSubjectVersionResponse, NewSchemaVersion, SchemaVersion, Subject}; 10 | 11 | #[derive(Debug, Identifiable, Queryable)] 12 | #[diesel(table_name = schemas)] 13 | pub struct Schema { 14 | pub id: i64, 15 | pub fingerprint: String, 16 | pub json: String, 17 | pub created_at: NaiveDateTime, 18 | pub updated_at: NaiveDateTime, 19 | pub fingerprint2: Option, 20 | } 21 | 22 | #[derive(Debug, Insertable)] 23 | #[diesel(table_name = schemas)] 24 | pub struct NewSchema { 25 | pub fingerprint: String, 26 | pub json: String, 27 | pub created_at: NaiveDateTime, 28 | pub updated_at: NaiveDateTime, 29 | pub fingerprint2: Option, 30 | } 31 | 32 | #[derive(Debug, Serialize)] 33 | pub struct SchemaResponse { 34 | pub schema: String, 35 | } 36 | 37 | pub struct GetSchema { 38 | pub id: i64, 39 | } 40 | 41 | impl Schema { 42 | fn parse(data: String) -> Result { 43 | avro_rs::Schema::parse_str(&data) 44 | .map_err(|_| ApiError::new(ApiAvroErrorCode::InvalidAvroSchema)) 45 | } 46 | 47 | fn generate_fingerprint(data: String) -> Result { 48 | Ok(format!( 49 | "{}", 50 | Self::parse(data)?.fingerprint::() 51 | )) 52 | } 53 | 54 | pub(crate) fn is_compatible( 55 | readers_schema: &str, 56 | writers_schema: &str, 57 | ) -> Result { 58 | let writers_schema = avro_rs::Schema::parse_str(writers_schema) 59 | .map_err(|_| ApiError::new(ApiAvroErrorCode::InvalidAvroSchema))?; 60 | let readers_schema = avro_rs::Schema::parse_str(readers_schema) 61 | .map_err(|_| ApiError::new(ApiAvroErrorCode::InvalidAvroSchema))?; 62 | Ok(SchemaCompatibility::can_read(&writers_schema, &readers_schema).is_ok()) 63 | } 64 | 65 | pub fn find_by_fingerprint( 66 | conn: &mut PgConnection, 67 | fingerprint: String, 68 | ) -> Result, ApiError> { 69 | use super::schema::schemas::dsl::{fingerprint2, schemas}; 70 | Ok(schemas 71 | .filter(fingerprint2.eq(fingerprint)) 72 | .load::(conn) 73 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError))? 74 | .pop()) 75 | } 76 | 77 | pub fn register_new_version( 78 | conn: &mut PgConnection, 79 | registration: RegisterSchema, 80 | ) -> Result { 81 | let (subject, json) = (registration.subject, registration.schema); 82 | let fingerprint = Self::generate_fingerprint(json.to_owned())?; 83 | 84 | conn.transaction::<_, ApiError, _>(|conn| { 85 | let db_schema = Self::find_by_fingerprint(conn, fingerprint.to_owned())?; 86 | match db_schema { 87 | Some(s) => { 88 | match SchemaVersion::with_schema_and_subject(conn, subject.to_owned(), s.id)? { 89 | 1 => Ok(s), 90 | _ => Self::create_new_version(conn, None, fingerprint, subject, Some(s)), 91 | } 92 | } 93 | None => Self::create_new_version(conn, Some(json), fingerprint, subject, None), 94 | } 95 | }) 96 | } 97 | 98 | fn create_new_version( 99 | conn: &mut PgConnection, 100 | json: Option, 101 | fingerprint: String, 102 | subject_name: String, 103 | db_schema: Option, 104 | ) -> Result { 105 | let latest = 106 | SchemaVersion::latest_version_with_subject_name(conn, subject_name.to_owned())?; 107 | 108 | // If it already exists, we don't care, we just update and get the subject. 109 | let subject = Subject::insert(conn, subject_name)?; 110 | let (schema, new_version) = match latest { 111 | Some(latest_version) => { 112 | // TODO: Check compatibility first - implementation should be mostly in 113 | // https://github.com/flavray/avro-rs 114 | let sch = match json { 115 | Some(j) => Self::new(conn, j, fingerprint)?, 116 | None => db_schema 117 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError))?, 118 | }; 119 | (sch, latest_version + 1) 120 | } 121 | None => { 122 | // Create schema version for subject 123 | let sch = match json { 124 | Some(j) => Self::new(conn, j, fingerprint)?, 125 | None => db_schema 126 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError))?, 127 | }; 128 | (sch, 1) 129 | } 130 | }; 131 | 132 | SchemaVersion::insert( 133 | conn, 134 | NewSchemaVersion { 135 | version: Some(new_version), 136 | subject_id: subject.id, 137 | schema_id: schema.id, 138 | }, 139 | )?; 140 | // TODO: set compatibility 141 | Ok(schema) 142 | } 143 | 144 | pub fn new( 145 | conn: &mut PgConnection, 146 | json: String, 147 | fingerprint: String, 148 | ) -> Result { 149 | // TODO: we use the same in both fields. This means we don't do the same as 150 | // salsify 151 | let new_schema = NewSchema { 152 | json, 153 | fingerprint: fingerprint.to_owned(), 154 | fingerprint2: Some(fingerprint), 155 | created_at: Utc::now().naive_utc(), 156 | updated_at: Utc::now().naive_utc(), 157 | }; 158 | 159 | Self::insert(conn, new_schema) 160 | } 161 | 162 | pub fn insert(conn: &mut PgConnection, schema: NewSchema) -> Result { 163 | use super::schema::schemas::dsl::*; 164 | diesel::insert_into(schemas) 165 | .values(&schema) 166 | .get_result::(conn) 167 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 168 | } 169 | 170 | pub fn get_by_json(conn: &mut PgConnection, data: String) -> Result { 171 | use super::schema::schemas::dsl::*; 172 | schemas 173 | .filter(json.eq(data)) 174 | .get_result::(conn) 175 | .map_err(|_| ApiError::new(ApiAvroErrorCode::SchemaNotFound)) 176 | } 177 | 178 | pub fn get_by_id(conn: &mut PgConnection, schema_id: i64) -> Result { 179 | use super::schema::schemas::dsl::*; 180 | schemas 181 | .find(schema_id) 182 | .get_result::(conn) 183 | .map_err(|_| ApiError::new(ApiAvroErrorCode::SchemaNotFound)) 184 | } 185 | 186 | pub fn verify_registration( 187 | conn: &mut PgConnection, 188 | subject_name: String, 189 | schema_json: String, 190 | ) -> Result { 191 | conn.transaction::<_, ApiError, _>(|conn| { 192 | Subject::get_by_name(conn, subject_name.to_string()).and_then(|subject| { 193 | Self::get_by_json(conn, schema_json.to_string()).and_then(|schema| { 194 | SchemaVersion::find(conn, subject.id, schema.id).and_then(|schema_version| { 195 | Ok(VerifyRegistrationResponse { 196 | subject: subject.name, 197 | id: schema.id, 198 | version: schema_version 199 | .version 200 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::VersionNotFound))?, 201 | schema: schema.json, 202 | }) 203 | }) 204 | }) 205 | }) 206 | }) 207 | } 208 | } 209 | 210 | #[derive(Debug, Serialize)] 211 | pub struct RegisterSchemaResponse { 212 | pub id: String, 213 | } 214 | 215 | pub struct RegisterSchema { 216 | pub subject: String, 217 | pub schema: String, 218 | } 219 | 220 | pub struct VerifySchemaRegistration { 221 | pub subject: String, 222 | pub schema: String, 223 | } 224 | 225 | pub type VerifyRegistrationResponse = GetSubjectVersionResponse; 226 | -------------------------------------------------------------------------------- /src/db/models/configs.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str; 3 | 4 | use chrono::NaiveDateTime; 5 | use diesel::prelude::*; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 9 | 10 | use super::schema::*; 11 | use super::Subject; 12 | 13 | #[derive(Debug, Identifiable, Queryable, Associations, Serialize)] 14 | #[diesel(table_name = configs)] 15 | #[diesel(belongs_to(Subject))] 16 | pub struct Config { 17 | pub id: i64, 18 | pub compatibility: Option, 19 | pub created_at: NaiveDateTime, 20 | pub updated_at: NaiveDateTime, 21 | pub subject_id: Option, 22 | } 23 | 24 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 25 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 26 | pub enum CompatibilityLevel { 27 | Backward, 28 | BackwardTransitive, 29 | Forward, 30 | ForwardTransitive, 31 | Full, 32 | FullTransitive, 33 | #[serde(rename = "NONE")] 34 | CompatNone, 35 | #[serde(other)] 36 | Unknown, 37 | } 38 | 39 | impl fmt::Display for CompatibilityLevel { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | let screaming_snake_case = match self { 42 | Self::Backward => Ok("BACKWARD"), 43 | Self::BackwardTransitive => Ok("BACKWARD_TRANSITIVE"), 44 | Self::Forward => Ok("FORWARD"), 45 | Self::ForwardTransitive => Ok("FORWARD_TRANSITIVE"), 46 | Self::Full => Ok("FULL"), 47 | Self::FullTransitive => Ok("FULL_TRANSITIVE"), 48 | Self::CompatNone => Ok("NONE"), 49 | // This won't ever be parsed, so we're fine by leaving this empty 50 | _ => Ok(""), 51 | }?; 52 | write!(f, "{}", screaming_snake_case) 53 | } 54 | } 55 | 56 | impl str::FromStr for CompatibilityLevel { 57 | type Err = (); 58 | 59 | fn from_str(s: &str) -> Result { 60 | match s { 61 | "BACKWARD" => Ok(Self::Backward), 62 | "BACKWARD_TRANSITIVE" => Ok(Self::BackwardTransitive), 63 | "FORWARD" => Ok(Self::Forward), 64 | "FORWARD_TRANSITIVE" => Ok(Self::ForwardTransitive), 65 | "FULL" => Ok(Self::Full), 66 | "FULL_TRANSITIVE" => Ok(Self::FullTransitive), 67 | "NONE" => Ok(Self::CompatNone), 68 | _ => Err(()), 69 | } 70 | } 71 | } 72 | 73 | impl CompatibilityLevel { 74 | /// Returns [`Ok`] value of `self` if the `CompatibilityLevel is valid, otherwise 75 | /// returns the [`Err`] of `InvalidCompatibilitylevel` 76 | /// 77 | /// [`Ok`]: enum.Result.html#variant.Ok 78 | /// [`Err`]: enum.Result.html#variant.Err 79 | pub fn valid(self) -> Result { 80 | ConfigCompatibility::new(self.to_string()).and(Ok(self)) 81 | } 82 | } 83 | 84 | #[derive(Debug, Serialize, Deserialize)] 85 | pub struct ConfigCompatibility { 86 | pub compatibility: CompatibilityLevel, 87 | } 88 | 89 | impl ConfigCompatibility { 90 | pub fn new(level: String) -> Result { 91 | match level.parse::() { 92 | Ok(l) => Ok(Self { compatibility: l }), 93 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::InvalidCompatibilityLevel)), 94 | } 95 | } 96 | } 97 | 98 | // Just to be clearer when we're implementing the Handler 99 | pub type SetConfig = ConfigCompatibility; 100 | 101 | pub struct GetSubjectConfig { 102 | pub subject: String, 103 | } 104 | 105 | pub struct SetSubjectConfig { 106 | pub subject: String, 107 | pub compatibility: CompatibilityLevel, 108 | } 109 | 110 | impl Config { 111 | pub const DEFAULT_COMPATIBILITY: CompatibilityLevel = CompatibilityLevel::Backward; 112 | 113 | /// Retrieves the global compatibility level 114 | /// 115 | /// *NOTE*: if there is no global compatibility level, it sets it to the default 116 | /// compatibility level 117 | pub fn get_global_compatibility(conn: &mut PgConnection) -> Result { 118 | use super::schema::configs::dsl::*; 119 | match configs.filter(id.eq(0)).get_result::(conn) { 120 | // This should always return ok. If it doesn't, that means someone manually 121 | // edited the configs entry with id 0. Not only that, but they set the column 122 | // compatibility to NULL. Because of that, we don't try to fix it (although we 123 | // probably could) and instead return an internal server error. 124 | Ok(config) => config 125 | .compatibility 126 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 127 | Err(diesel::result::Error::NotFound) => { 128 | // If we didn't find an entry with id 0, then this is either: 129 | // 130 | // a) first time we try to get a config, so we should set a default 131 | // b) database was manually modified and we should set a default again 132 | Self::insert(&Self::DEFAULT_COMPATIBILITY.to_string(), conn)?; 133 | Ok(Self::DEFAULT_COMPATIBILITY.to_string()) 134 | } 135 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 136 | } 137 | } 138 | 139 | pub fn get_with_subject_name( 140 | conn: &mut PgConnection, 141 | subject_name: String, 142 | ) -> Result { 143 | let subject = Subject::get_by_name(conn, subject_name)?; 144 | match Self::belonging_to(&subject).get_result::(conn) { 145 | // This should always return ok. If it doesn't, that means someone manually 146 | // edited the configs entry with id 0. Not only that, but they set the column 147 | // compatibility to NULL. Because of that, we don't try to fix it (although we 148 | // probably could) and instead return an internal server error. 149 | Ok(config) => config 150 | .compatibility 151 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 152 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 153 | } 154 | } 155 | 156 | pub fn set_with_subject_name( 157 | conn: &mut PgConnection, 158 | subject_name: String, 159 | compat: String, 160 | ) -> Result { 161 | use super::schema::configs::dsl::*; 162 | 163 | let subject = Subject::get_by_name(conn, subject_name)?; 164 | match Self::belonging_to(&subject).get_result::(conn) { 165 | Ok(config) => { 166 | match diesel::update(&config) 167 | .set(compatibility.eq(&compat)) 168 | .get_result::(conn) 169 | { 170 | Ok(conf) => conf 171 | .compatibility 172 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 173 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 174 | } 175 | } 176 | Err(diesel::result::Error::NotFound) => { 177 | diesel::insert_into(configs) 178 | .values(( 179 | compatibility.eq(&compat), 180 | created_at.eq(diesel::dsl::now), 181 | updated_at.eq(diesel::dsl::now), 182 | subject_id.eq(subject.id), 183 | )) 184 | .execute(conn) 185 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError))?; 186 | Ok(compat) 187 | } 188 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 189 | } 190 | } 191 | 192 | /// Updates the global compatibility level 193 | /// 194 | /// *NOTE*: if there is no global compatibility level, it sets it to the level passed 195 | pub fn set_global_compatibility( 196 | conn: &mut PgConnection, 197 | compat: &str, 198 | ) -> Result { 199 | use super::schema::configs::dsl::*; 200 | 201 | match diesel::update(configs.find(0)) 202 | .set(compatibility.eq(compat)) 203 | .get_result::(conn) 204 | { 205 | Ok(config) => config 206 | .compatibility 207 | .ok_or_else(|| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 208 | Err(diesel::result::Error::NotFound) => { 209 | // If we didn't find an entry with id 0, then this is either: 210 | // 211 | // a) first time we try to get a config, so we should set a default 212 | // b) database was manually modified and we should set a default again 213 | Self::insert(compat, conn)?; 214 | Ok(compat.to_string()) 215 | } 216 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 217 | } 218 | } 219 | 220 | fn insert(compat: &str, conn: &mut PgConnection) -> Result { 221 | use super::schema::configs::dsl::*; 222 | 223 | diesel::insert_into(configs) 224 | .values(( 225 | id.eq(0), 226 | compatibility.eq(&compat), 227 | created_at.eq(diesel::dsl::now), 228 | updated_at.eq(diesel::dsl::now), 229 | )) 230 | .execute(conn) 231 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/db/models/schema_versions.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use log::error; 3 | 4 | use super::schema::*; 5 | use super::schemas::Schema; 6 | use super::subjects::Subject; 7 | 8 | use crate::api::errors::{ApiAvroErrorCode, ApiError}; 9 | 10 | #[derive(Debug, Identifiable, Associations, Queryable)] 11 | #[diesel(table_name = schema_versions)] 12 | #[diesel(belongs_to(Schema))] 13 | #[diesel(belongs_to(Subject))] 14 | pub struct SchemaVersion { 15 | pub id: i64, 16 | pub version: Option, 17 | pub subject_id: i64, 18 | pub schema_id: i64, 19 | } 20 | 21 | #[derive(Debug, Insertable)] 22 | #[diesel(table_name = schema_versions)] 23 | pub struct NewSchemaVersion { 24 | pub version: Option, 25 | pub subject_id: i64, 26 | pub schema_id: i64, 27 | } 28 | 29 | pub type SchemaVersionFields = NewSchemaVersion; 30 | 31 | impl SchemaVersion { 32 | pub fn insert(conn: &mut PgConnection, sv: NewSchemaVersion) -> Result { 33 | use super::schema::schema_versions::dsl::schema_versions; 34 | diesel::insert_into(schema_versions) 35 | .values(&sv) 36 | .get_result::(conn) 37 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 38 | } 39 | 40 | pub fn find( 41 | conn: &mut PgConnection, 42 | find_subject_id: i64, 43 | find_schema_id: i64, 44 | ) -> Result { 45 | use super::schema::schema_versions::dsl::{schema_id, schema_versions, subject_id}; 46 | 47 | schema_versions 48 | .filter(subject_id.eq(find_subject_id)) 49 | .filter(schema_id.eq(find_schema_id)) 50 | .get_result::(conn) 51 | .map_err(|_| ApiError::new(ApiAvroErrorCode::VersionNotFound)) 52 | } 53 | 54 | pub fn with_schema_and_subject( 55 | conn: &mut PgConnection, 56 | search_subject_name: String, 57 | search_schema_id: i64, 58 | ) -> Result { 59 | use super::schema::schema_versions::dsl::{id, schema_id, schema_versions, subject_id}; 60 | use super::schema::schemas::dsl::{id as schemas_id, schemas}; 61 | use super::schema::subjects::dsl::{id as subjects_id, name as subject_name, subjects}; 62 | 63 | schema_versions 64 | .inner_join(subjects.on(subject_id.eq(subjects_id))) 65 | .inner_join(schemas.on(schema_id.eq(schemas_id))) 66 | .filter(subject_name.eq(search_subject_name)) 67 | .filter(schema_id.eq(search_schema_id)) 68 | .select(id) 69 | .execute(conn) 70 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 71 | } 72 | 73 | pub fn versions_with_subject_name( 74 | conn: &mut PgConnection, 75 | subject_name: String, 76 | ) -> Result>, ApiError> { 77 | use super::schema::schema_versions::dsl::{schema_versions, subject_id, version}; 78 | use super::schema::subjects::dsl::{id as subjects_id, name, subjects}; 79 | 80 | match schema_versions 81 | .inner_join(subjects.on(subject_id.eq(subjects_id))) 82 | .filter(name.eq(&subject_name)) 83 | .select(version) 84 | .order(version.asc()) 85 | .load::>(conn) 86 | { 87 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 88 | Ok(versions) => { 89 | if versions.is_empty() { 90 | Err(ApiError::new(ApiAvroErrorCode::SubjectNotFound)) 91 | } else { 92 | Ok(versions) 93 | } 94 | } 95 | } 96 | } 97 | 98 | pub fn latest_version_with_subject_name( 99 | conn: &mut PgConnection, 100 | subject_name: String, 101 | ) -> Result, ApiError> { 102 | use super::schema::schema_versions::dsl::{schema_versions, subject_id, version}; 103 | use super::schema::subjects::dsl::{id as subjects_id, name, subjects}; 104 | 105 | let res = schema_versions 106 | .inner_join(subjects.on(subject_id.eq(subjects_id))) 107 | .filter(name.eq(&subject_name)) 108 | .select(version) 109 | .order(version.desc()) 110 | .first::>(conn); 111 | 112 | match res { 113 | Ok(v) => Ok(v), 114 | Err(diesel::NotFound) => Ok(None), 115 | _ => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 116 | } 117 | } 118 | 119 | pub fn get_schema_id_from_latest( 120 | conn: &mut PgConnection, 121 | subject_name: String, 122 | ) -> Result<(i64, i32, String), ApiError> { 123 | use super::schema::schema_versions::dsl::{ 124 | schema_id, schema_versions, subject_id, version, 125 | }; 126 | use super::schema::schemas::dsl::{json, schemas}; 127 | 128 | conn.transaction::<_, ApiError, _>(|conn| { 129 | let subject = Subject::get_by_name(conn, subject_name)?; 130 | 131 | let (schema_version, schema_id_result): (Option, i64) = match schema_versions 132 | .filter(subject_id.eq(subject.id)) 133 | .order(version.desc()) 134 | .select((version, schema_id)) 135 | .first(conn) 136 | { 137 | Err(diesel::result::Error::NotFound) => { 138 | Err(ApiError::new(ApiAvroErrorCode::VersionNotFound)) 139 | } 140 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 141 | Ok(o) => Ok(o), 142 | }?; 143 | 144 | let schema_json = match schemas.find(schema_id_result).select(json).first(conn) { 145 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 146 | Ok(o) => Ok(o), 147 | }?; 148 | 149 | Ok(( 150 | schema_id_result, 151 | schema_version.ok_or_else(|| ApiError::new(ApiAvroErrorCode::VersionNotFound))?, 152 | schema_json, 153 | )) 154 | }) 155 | } 156 | 157 | /// TODO: This should return a struct, not a tuple as it's then hard to interface with 158 | /// this method 159 | pub fn get_schema_id( 160 | conn: &mut PgConnection, 161 | subject_name: String, 162 | schema_version: u32, 163 | ) -> Result<(i64, i32, String), ApiError> { 164 | use super::schema::schema_versions::dsl::{ 165 | schema_id, schema_versions, subject_id, version, 166 | }; 167 | use super::schema::schemas::dsl::{json, schemas}; 168 | 169 | conn.transaction::<_, ApiError, _>(|conn| { 170 | let subject = Subject::get_by_name(conn, subject_name)?; 171 | 172 | let schema_id_result = match schema_versions 173 | .filter(subject_id.eq(subject.id)) 174 | .filter(version.eq(Some(schema_version as i32))) 175 | .select(schema_id) 176 | .first(conn) 177 | { 178 | Err(diesel::result::Error::NotFound) => { 179 | Err(ApiError::new(ApiAvroErrorCode::VersionNotFound)) 180 | } 181 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 182 | Ok(o) => Ok(o), 183 | }?; 184 | 185 | let schema_json = match schemas.find(schema_id_result).select(json).first(conn) { 186 | Err(_) => Err(ApiError::new(ApiAvroErrorCode::BackendDatastoreError)), 187 | Ok(o) => Ok(o), 188 | }?; 189 | 190 | Ok((schema_id_result, schema_version as i32, schema_json)) 191 | }) 192 | } 193 | 194 | pub fn delete_subject_with_name( 195 | conn: &mut PgConnection, 196 | subject: String, 197 | ) -> Result>, diesel::result::Error> { 198 | use super::schema::schema_versions::dsl::{ 199 | id, schema_id, schema_versions, subject_id, version, 200 | }; 201 | use super::schema::schemas::dsl::{id as schemas_id, schemas}; 202 | use super::schema::subjects::dsl::{id as subjects_id, name, subjects}; 203 | 204 | conn.transaction::<_, diesel::result::Error, _>(|conn| { 205 | Ok(schema_versions 206 | .inner_join(subjects.on(subject_id.eq(subjects_id))) 207 | .inner_join(schemas.on(schema_id.eq(schemas_id))) 208 | .filter(name.eq(&subject)) 209 | .select((id, version, subject_id, schema_id)) 210 | .load::(conn)? 211 | .into_iter() 212 | .map(|entry| { 213 | if let Err(e) = entry.delete(conn) { 214 | error!("error deleting: {}", e); 215 | } 216 | entry.version 217 | }) 218 | .collect()) 219 | }) 220 | } 221 | 222 | fn delete(&self, conn: &mut PgConnection) -> Result<(), diesel::result::Error> { 223 | use super::schema::configs::dsl::{configs, subject_id}; 224 | use super::schema::schema_versions::dsl::{id, schema_versions}; 225 | use super::schema::schemas::dsl::{id as schemas_id, schemas}; 226 | use super::schema::subjects::dsl::{id as subjects_id, subjects}; 227 | 228 | conn.transaction::<_, diesel::result::Error, _>(|conn| { 229 | let schemas_delete = schemas.filter(schemas_id.eq(self.schema_id)); 230 | let subjects_delete = subjects.filter(subjects_id.eq(self.subject_id)); 231 | let schema_versions_delete = schema_versions.filter(id.eq(self.id)); 232 | let configs_delete = configs.filter(subject_id.eq(self.subject_id)); 233 | 234 | diesel::delete(schemas_delete).execute(conn)?; 235 | diesel::delete(subjects_delete).execute(conn)?; 236 | diesel::delete(schema_versions_delete).execute(conn)?; 237 | diesel::delete(configs_delete).execute(conn)?; 238 | 239 | Ok(()) 240 | }) 241 | } 242 | 243 | pub fn delete_version_with_subject( 244 | conn: &mut PgConnection, 245 | request: DeleteSchemaVersion, 246 | ) -> Result { 247 | use super::schema::schema_versions::dsl::version; 248 | 249 | let (subject, v) = (request.subject, request.version); 250 | 251 | conn.transaction::<_, ApiError, _>(|conn| { 252 | Subject::get_by_name(conn, subject.to_owned()).and_then(|subject| { 253 | diesel::delete(Self::belonging_to(&subject).filter(version.eq(v as i32))) 254 | .execute(conn) 255 | .map_err(|_| ApiError::new(ApiAvroErrorCode::BackendDatastoreError)) 256 | .and_then(|o| match o { 257 | 0 => Err(ApiError::new(ApiAvroErrorCode::VersionNotFound)), 258 | _ => Ok(v), 259 | }) 260 | }) 261 | }) 262 | } 263 | } 264 | 265 | pub struct DeleteSchemaVersion { 266 | pub subject: String, 267 | pub version: u32, 268 | } 269 | -------------------------------------------------------------------------------- /tests/subject.rs: -------------------------------------------------------------------------------- 1 | use actix_web::http; 2 | 3 | use crate::common::server::setup; 4 | use crate::db::DbAuxOperations; 5 | 6 | use avro_schema_registry::api::SchemaBody; 7 | 8 | #[actix_rt::test] 9 | async fn test_get_subjects_without_subjects() { 10 | let (server, mut conn) = setup(); 11 | conn.reset_subjects(); 12 | // returns empty list 13 | server 14 | .test( 15 | http::Method::GET, 16 | "/subjects", 17 | None, 18 | http::StatusCode::OK, 19 | r"\[\]", 20 | ) 21 | .await; 22 | } 23 | 24 | #[actix_rt::test] 25 | async fn test_get_subjects_with_subjects() { 26 | let (server, mut conn) = setup(); 27 | conn.reset_subjects(); 28 | conn.add_subjects(vec![String::from("subject1"), String::from("subject2")]); 29 | 30 | // it returns list of subjects 31 | server 32 | .test( 33 | http::Method::GET, 34 | "/subjects", 35 | None, 36 | http::StatusCode::OK, 37 | r#"\["subject1","subject2"\]"#, 38 | ) 39 | .await; 40 | } 41 | 42 | #[actix_rt::test] 43 | async fn test_get_versions_under_subject_without_subject() { 44 | let (server, _) = setup(); 45 | // it returns 404 with 'Subject not found' 46 | server 47 | .test( 48 | http::Method::GET, 49 | "/subjects/test.subject/versions", 50 | None, 51 | http::StatusCode::NOT_FOUND, 52 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 53 | ) 54 | .await; 55 | } 56 | 57 | #[actix_rt::test] 58 | async fn test_get_versions_under_subject_without_versions() { 59 | let (server, mut conn) = setup(); 60 | conn.add_subjects(vec![String::from("test.subject")]); 61 | 62 | // This should never happen with correct usage of the API 63 | // it returns 404 with 'Subject not found' 64 | server 65 | .test( 66 | http::Method::GET, 67 | "/subjects/test.subject/versions", 68 | None, 69 | http::StatusCode::NOT_FOUND, 70 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 71 | ) 72 | .await; 73 | 74 | conn.reset_subjects(); 75 | } 76 | 77 | #[actix_rt::test] 78 | async fn test_get_versions_under_subject_with_versions() { 79 | let (server, _) = setup(); 80 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 81 | let schema = SchemaBody { schema: schema_s }; 82 | server 83 | .test( 84 | http::Method::POST, 85 | "/subjects/test.subject/versions", 86 | Some(json!(schema)), 87 | http::StatusCode::OK, 88 | r#"\{"id":"\d+"\}"#, 89 | ) 90 | .await; 91 | 92 | // it returns list of one 93 | // TODO(nlopes): dangerous, postgresql can pick any other ID 94 | server 95 | .test( 96 | http::Method::GET, 97 | "/subjects/test.subject/versions", 98 | None, 99 | http::StatusCode::OK, 100 | r"\[1\]", 101 | ) 102 | .await; 103 | 104 | // it returns list of many 105 | let schema2_s = std::fs::read_to_string("tests/fixtures/schema2.json").unwrap(); 106 | let schema2 = SchemaBody { schema: schema2_s }; 107 | 108 | // This modifies the database state in preparation for the next request 109 | server 110 | .test( 111 | http::Method::POST, 112 | "/subjects/test.subject/versions", 113 | Some(json!(schema2)), 114 | http::StatusCode::OK, 115 | r#"\{"id":"\d+"\}"#, 116 | ) 117 | .await; 118 | 119 | server 120 | .test( 121 | http::Method::GET, 122 | "/subjects/test.subject/versions", 123 | None, 124 | http::StatusCode::OK, 125 | r"\[1,2\]", 126 | ) 127 | .await; 128 | } 129 | 130 | #[actix_rt::test] 131 | async fn test_delete_subject_without_subject() { 132 | let (server, _) = setup(); 133 | // it returns 404 with 'Subject not found' 134 | server 135 | .test( 136 | http::Method::DELETE, 137 | "/subjects/test.subject", 138 | None, 139 | http::StatusCode::NOT_FOUND, 140 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 141 | ) 142 | .await; 143 | } 144 | 145 | #[actix_rt::test] 146 | async fn test_delete_subject_with_subject() { 147 | let (server, _) = setup(); 148 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 149 | let schema = SchemaBody { schema: schema_s }; 150 | server 151 | .test( 152 | http::Method::POST, 153 | "/subjects/test.subject/versions", 154 | Some(json!(schema)), 155 | http::StatusCode::OK, 156 | r#"\{"id":"\d+"\}"#, 157 | ) 158 | .await; 159 | 160 | // it returns list with versions of schemas deleted 161 | server 162 | .test( 163 | http::Method::DELETE, 164 | "/subjects/test.subject", 165 | None, 166 | http::StatusCode::OK, 167 | r"\[1\]", 168 | ) 169 | .await; 170 | } 171 | 172 | #[actix_rt::test] 173 | async fn test_get_version_of_schema_registered_under_subject_without_subject() { 174 | let (server, _) = setup(); 175 | // it returns 404 with 'Subject not found' 176 | server 177 | .test( 178 | http::Method::GET, 179 | "/subjects/test.subject/versions/1", 180 | None, 181 | http::StatusCode::NOT_FOUND, 182 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 183 | ) 184 | .await; 185 | } 186 | 187 | #[actix_rt::test] 188 | async fn test_get_version_of_schema_registered_under_subject_with_subject() { 189 | let (server, _) = setup(); 190 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 191 | let schema = SchemaBody { schema: schema_s }; 192 | server 193 | .test( 194 | http::Method::POST, 195 | "/subjects/test.subject/versions", 196 | Some(json!(schema)), 197 | http::StatusCode::OK, 198 | r#"\{"id":"\d+"\}"#, 199 | ) 200 | .await; 201 | 202 | // with non existing version it returns 404 with 'Version not found' 203 | server 204 | .test( 205 | http::Method::GET, 206 | "/subjects/test.subject/versions/2", 207 | None, 208 | http::StatusCode::NOT_FOUND, 209 | r#"\{"error_code":40402,"message":"Version not found"\}"#, 210 | ) 211 | .await; 212 | // with version out of bounds it returns 422 with 'Invalid version' for lower bound 213 | server 214 | .test( 215 | http::Method::GET, 216 | "/subjects/test.subject/versions/0", 217 | None, 218 | http::StatusCode::UNPROCESSABLE_ENTITY, 219 | r#"\{"error_code":42202,"message":"Invalid version"\}"#, 220 | ) 221 | .await; 222 | 223 | // with version out of bounds it returns 422 with 'Invalid version' for upper bound 224 | server 225 | .test( 226 | http::Method::GET, 227 | "/subjects/test.subject/versions/2147483648", 228 | None, 229 | http::StatusCode::UNPROCESSABLE_ENTITY, 230 | r#"\{"error_code":42202,"message":"Invalid version"\}"#, 231 | ) 232 | .await; 233 | 234 | let subject_regex = r#"\{"subject":"test.subject","id":\d+,"version":1,"schema":"\{ \\"type\\": \\"record\\", \\"name\\": \\"test\\", \\"fields\\": \[ \{ \\"type\\": \\"string\\", \\"name\\": \\"field1\\", \\"default\\": \\"\\" \}, \{ \\"type\\": \\"string\\", \\"name\\": \\"field2\\" \} \]\}"\}"#; 235 | server 236 | .test( 237 | http::Method::GET, 238 | "/subjects/test.subject/versions/latest", 239 | None, 240 | http::StatusCode::OK, 241 | subject_regex, 242 | ) 243 | .await; 244 | 245 | server 246 | .test( 247 | http::Method::GET, 248 | "/subjects/test.subject/versions/1", 249 | None, 250 | http::StatusCode::OK, 251 | subject_regex, 252 | ) 253 | .await; 254 | } 255 | 256 | #[actix_rt::test] 257 | async fn test_register_schema_under_subject_with_valid_schema() { 258 | let (server, _) = setup(); 259 | 260 | // it returns schema identifier 261 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 262 | let schema = SchemaBody { schema: schema_s }; 263 | 264 | server 265 | .test( 266 | http::Method::POST, 267 | "/subjects/test.subject/versions", 268 | Some(json!(schema)), 269 | http::StatusCode::OK, 270 | r#"\{"id":"\d+"\}"#, 271 | ) 272 | .await; 273 | } 274 | 275 | #[actix_rt::test] 276 | async fn test_register_schema_under_subject_with_invalid_schema() { 277 | let (server, _) = setup(); 278 | let schema = SchemaBody { 279 | schema: "{}".to_string(), 280 | }; 281 | 282 | // it returns 422 with 'Invalid Avro schema' 283 | server 284 | .test( 285 | http::Method::POST, 286 | "/subjects/test.subject/versions", 287 | Some(json!(schema)), 288 | http::StatusCode::UNPROCESSABLE_ENTITY, 289 | r#"\{"error_code":42201,"message":"Invalid Avro schema"\}"#, 290 | ) 291 | .await; 292 | } 293 | 294 | #[actix_rt::test] 295 | async fn test_check_schema_registration_without_subject() { 296 | let (server, _) = setup(); 297 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 298 | let schema = SchemaBody { schema: schema_s }; 299 | 300 | // it returns 404 with 'Subject not found' 301 | server 302 | .test( 303 | http::Method::POST, 304 | "/subjects/test.subject", 305 | Some(json!(schema)), 306 | http::StatusCode::NOT_FOUND, 307 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 308 | ) 309 | .await; 310 | } 311 | 312 | #[actix_rt::test] 313 | async fn test_check_schema_registration_with_subject_but_different_schema() { 314 | let (server, _) = setup(); 315 | 316 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 317 | let schema2_s = std::fs::read_to_string("tests/fixtures/schema2.json").unwrap(); 318 | let schema = SchemaBody { schema: schema_s }; 319 | let schema2 = SchemaBody { schema: schema2_s }; 320 | 321 | // setup of schema 2 322 | server 323 | .test( 324 | http::Method::POST, 325 | "/subjects/test.subject/versions", 326 | Some(json!(schema2)), 327 | http::StatusCode::OK, 328 | r#"\{"id":"\d+"\}"#, 329 | ) 330 | .await; 331 | 332 | // it returns 404 with Subject not found 333 | server 334 | .test( 335 | http::Method::POST, 336 | "/subjects/test.subject", 337 | Some(json!(schema)), 338 | http::StatusCode::NOT_FOUND, 339 | r#"\{"error_code":40403,"message":"Schema not found"\}"#, 340 | ) 341 | .await; 342 | } 343 | 344 | #[actix_rt::test] 345 | async fn test_delete_schema_version_under_subject_without_subject() { 346 | let (server, _) = setup(); 347 | // it returns 404 with 'Subject not found' 348 | server 349 | .test( 350 | http::Method::DELETE, 351 | "/subjects/test.subject/versions/1", 352 | None, 353 | http::StatusCode::NOT_FOUND, 354 | r#"\{"error_code":40401,"message":"Subject not found"\}"#, 355 | ) 356 | .await; 357 | } 358 | #[actix_rt::test] 359 | async fn test_delete_schema_version_under_subject_with_subject() { 360 | let (server, _) = setup(); 361 | // setup 362 | let schema_s = std::fs::read_to_string("tests/fixtures/schema.json").unwrap(); 363 | let schema = SchemaBody { schema: schema_s }; 364 | server 365 | .test( 366 | http::Method::POST, 367 | "/subjects/test.subject/versions", 368 | Some(json!(schema)), 369 | http::StatusCode::OK, 370 | r#"\{"id":"\d+"\}"#, 371 | ) 372 | .await; 373 | 374 | // with non existing version it returns 404 with 'Version not found' 375 | server 376 | .test( 377 | http::Method::DELETE, 378 | "/subjects/test.subject/versions/2", 379 | None, 380 | http::StatusCode::NOT_FOUND, 381 | r#"\{"error_code":40402,"message":"Version not found"\}"#, 382 | ) 383 | .await; 384 | 385 | // with version out of bounds it returns 422 with 'Invalid version' 386 | server 387 | .test( 388 | http::Method::DELETE, 389 | "/subjects/test.subject/versions/0", 390 | None, 391 | http::StatusCode::UNPROCESSABLE_ENTITY, 392 | r#"\{"error_code":42202,"message":"Invalid version"\}"#, 393 | ) 394 | .await; 395 | // with existing version it returns list with versions of schemas deleted 396 | server 397 | .test( 398 | http::Method::DELETE, 399 | "/subjects/test.subject/versions/1", 400 | None, 401 | http::StatusCode::OK, 402 | "1", 403 | ) 404 | .await; 405 | 406 | // re-setup before testing with latest 407 | server 408 | .test( 409 | http::Method::POST, 410 | "/subjects/test.subject/versions", 411 | Some(json!(schema)), 412 | http::StatusCode::OK, 413 | r#"\{"id":"\d+"\}"#, 414 | ) 415 | .await; 416 | // with latest version and only one version it returns version of schema deleted 417 | server 418 | .test( 419 | http::Method::DELETE, 420 | "/subjects/test.subject/versions/latest", 421 | None, 422 | http::StatusCode::OK, 423 | "1", 424 | ) 425 | .await; 426 | // setup for next test 427 | server 428 | .test( 429 | http::Method::POST, 430 | "/subjects/test.subject/versions", 431 | Some(json!(schema)), 432 | http::StatusCode::OK, 433 | r#"\{"id":"\d+"\}"#, 434 | ) 435 | .await; 436 | let schema_s = std::fs::read_to_string("tests/fixtures/schema2.json").unwrap(); 437 | let schema = SchemaBody { schema: schema_s }; 438 | server 439 | .test( 440 | http::Method::POST, 441 | "/subjects/test.subject/versions", 442 | Some(json!(schema)), 443 | http::StatusCode::OK, 444 | r#"\{"id":"\d+"\}"#, 445 | ) 446 | .await; 447 | // with latest version and with multiple versions it returns version of schema deleted 448 | server 449 | .test( 450 | http::Method::DELETE, 451 | "/subjects/test.subject/versions/latest", 452 | None, 453 | http::StatusCode::OK, 454 | "2", 455 | ) 456 | .await; 457 | } 458 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "actix" 7 | version = "0.13.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" 10 | dependencies = [ 11 | "actix-macros", 12 | "actix-rt", 13 | "actix_derive", 14 | "bitflags 2.6.0", 15 | "bytes", 16 | "crossbeam-channel", 17 | "futures-core", 18 | "futures-sink", 19 | "futures-task", 20 | "futures-util", 21 | "log", 22 | "once_cell", 23 | "parking_lot 0.12.3", 24 | "pin-project-lite", 25 | "smallvec", 26 | "tokio", 27 | "tokio-util", 28 | ] 29 | 30 | [[package]] 31 | name = "actix-codec" 32 | version = "0.5.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" 35 | dependencies = [ 36 | "bitflags 2.6.0", 37 | "bytes", 38 | "futures-core", 39 | "futures-sink", 40 | "memchr", 41 | "pin-project-lite", 42 | "tokio", 43 | "tokio-util", 44 | "tracing", 45 | ] 46 | 47 | [[package]] 48 | name = "actix-http" 49 | version = "3.9.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" 52 | dependencies = [ 53 | "actix-codec", 54 | "actix-rt", 55 | "actix-service", 56 | "actix-utils", 57 | "ahash", 58 | "base64", 59 | "bitflags 2.6.0", 60 | "brotli", 61 | "bytes", 62 | "bytestring", 63 | "derive_more", 64 | "encoding_rs", 65 | "flate2", 66 | "futures-core", 67 | "h2", 68 | "http 0.2.12", 69 | "httparse", 70 | "httpdate", 71 | "itoa", 72 | "language-tags", 73 | "local-channel", 74 | "mime", 75 | "percent-encoding", 76 | "pin-project-lite", 77 | "rand", 78 | "sha1", 79 | "smallvec", 80 | "tokio", 81 | "tokio-util", 82 | "tracing", 83 | "zstd", 84 | ] 85 | 86 | [[package]] 87 | name = "actix-http-test" 88 | version = "3.2.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "061d27c2a6fea968fdaca0961ff429d23a4ec878c4f68f5d08626663ade69c80" 91 | dependencies = [ 92 | "actix-codec", 93 | "actix-rt", 94 | "actix-server", 95 | "actix-service", 96 | "actix-tls", 97 | "actix-utils", 98 | "awc", 99 | "bytes", 100 | "futures-core", 101 | "http 0.2.12", 102 | "log", 103 | "serde", 104 | "serde_json", 105 | "serde_urlencoded", 106 | "slab", 107 | "socket2", 108 | "tokio", 109 | ] 110 | 111 | [[package]] 112 | name = "actix-macros" 113 | version = "0.2.4" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" 116 | dependencies = [ 117 | "quote", 118 | "syn", 119 | ] 120 | 121 | [[package]] 122 | name = "actix-router" 123 | version = "0.5.3" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 126 | dependencies = [ 127 | "bytestring", 128 | "cfg-if", 129 | "http 0.2.12", 130 | "regex", 131 | "regex-lite", 132 | "serde", 133 | "tracing", 134 | ] 135 | 136 | [[package]] 137 | name = "actix-rt" 138 | version = "2.10.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" 141 | dependencies = [ 142 | "actix-macros", 143 | "futures-core", 144 | "tokio", 145 | ] 146 | 147 | [[package]] 148 | name = "actix-server" 149 | version = "2.5.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" 152 | dependencies = [ 153 | "actix-rt", 154 | "actix-service", 155 | "actix-utils", 156 | "futures-core", 157 | "futures-util", 158 | "mio", 159 | "socket2", 160 | "tokio", 161 | "tracing", 162 | ] 163 | 164 | [[package]] 165 | name = "actix-service" 166 | version = "2.0.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 169 | dependencies = [ 170 | "futures-core", 171 | "paste", 172 | "pin-project-lite", 173 | ] 174 | 175 | [[package]] 176 | name = "actix-test" 177 | version = "0.1.5" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "439022b5a7b5dac10798465029a9566e8e0cca7a6014541ed277b695691fac5f" 180 | dependencies = [ 181 | "actix-codec", 182 | "actix-http", 183 | "actix-http-test", 184 | "actix-rt", 185 | "actix-service", 186 | "actix-utils", 187 | "actix-web", 188 | "awc", 189 | "futures-core", 190 | "futures-util", 191 | "log", 192 | "serde", 193 | "serde_json", 194 | "serde_urlencoded", 195 | "tokio", 196 | ] 197 | 198 | [[package]] 199 | name = "actix-threadpool" 200 | version = "0.3.3" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" 203 | dependencies = [ 204 | "derive_more", 205 | "futures-channel", 206 | "lazy_static", 207 | "log", 208 | "num_cpus", 209 | "parking_lot 0.11.2", 210 | "threadpool", 211 | ] 212 | 213 | [[package]] 214 | name = "actix-tls" 215 | version = "3.4.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "ac453898d866cdbecdbc2334fe1738c747b4eba14a677261f2b768ba05329389" 218 | dependencies = [ 219 | "actix-rt", 220 | "actix-service", 221 | "actix-utils", 222 | "futures-core", 223 | "http 0.2.12", 224 | "http 1.1.0", 225 | "impl-more", 226 | "pin-project-lite", 227 | "tokio", 228 | "tokio-util", 229 | "tracing", 230 | ] 231 | 232 | [[package]] 233 | name = "actix-utils" 234 | version = "3.0.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" 237 | dependencies = [ 238 | "local-waker", 239 | "pin-project-lite", 240 | ] 241 | 242 | [[package]] 243 | name = "actix-web" 244 | version = "4.9.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" 247 | dependencies = [ 248 | "actix-codec", 249 | "actix-http", 250 | "actix-macros", 251 | "actix-router", 252 | "actix-rt", 253 | "actix-server", 254 | "actix-service", 255 | "actix-utils", 256 | "actix-web-codegen", 257 | "ahash", 258 | "bytes", 259 | "bytestring", 260 | "cfg-if", 261 | "cookie", 262 | "derive_more", 263 | "encoding_rs", 264 | "futures-core", 265 | "futures-util", 266 | "impl-more", 267 | "itoa", 268 | "language-tags", 269 | "log", 270 | "mime", 271 | "once_cell", 272 | "pin-project-lite", 273 | "regex", 274 | "regex-lite", 275 | "serde", 276 | "serde_json", 277 | "serde_urlencoded", 278 | "smallvec", 279 | "socket2", 280 | "time", 281 | "url", 282 | ] 283 | 284 | [[package]] 285 | name = "actix-web-codegen" 286 | version = "4.3.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" 289 | dependencies = [ 290 | "actix-router", 291 | "proc-macro2", 292 | "quote", 293 | "syn", 294 | ] 295 | 296 | [[package]] 297 | name = "actix-web-prom" 298 | version = "0.9.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "56a34f1825c3ae06567a9d632466809bbf34963c86002e8921b64f32d48d289d" 301 | dependencies = [ 302 | "actix-web", 303 | "futures-core", 304 | "log", 305 | "pin-project-lite", 306 | "prometheus", 307 | "regex", 308 | "strfmt", 309 | ] 310 | 311 | [[package]] 312 | name = "actix_derive" 313 | version = "0.6.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | ] 321 | 322 | [[package]] 323 | name = "addr2line" 324 | version = "0.22.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 327 | dependencies = [ 328 | "gimli", 329 | ] 330 | 331 | [[package]] 332 | name = "adler" 333 | version = "1.0.2" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 336 | 337 | [[package]] 338 | name = "adler2" 339 | version = "2.0.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 342 | 343 | [[package]] 344 | name = "adler32" 345 | version = "1.2.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 348 | 349 | [[package]] 350 | name = "ahash" 351 | version = "0.8.11" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 354 | dependencies = [ 355 | "cfg-if", 356 | "getrandom", 357 | "once_cell", 358 | "version_check", 359 | "zerocopy", 360 | ] 361 | 362 | [[package]] 363 | name = "aho-corasick" 364 | version = "1.1.3" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 367 | dependencies = [ 368 | "memchr", 369 | ] 370 | 371 | [[package]] 372 | name = "alloc-no-stdlib" 373 | version = "2.0.4" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 376 | 377 | [[package]] 378 | name = "alloc-stdlib" 379 | version = "0.2.2" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 382 | dependencies = [ 383 | "alloc-no-stdlib", 384 | ] 385 | 386 | [[package]] 387 | name = "allocator-api2" 388 | version = "0.2.18" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 391 | 392 | [[package]] 393 | name = "android-tzdata" 394 | version = "0.1.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 397 | 398 | [[package]] 399 | name = "android_system_properties" 400 | version = "0.1.5" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 403 | dependencies = [ 404 | "libc", 405 | ] 406 | 407 | [[package]] 408 | name = "anstream" 409 | version = "0.6.15" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 412 | dependencies = [ 413 | "anstyle", 414 | "anstyle-parse", 415 | "anstyle-query", 416 | "anstyle-wincon", 417 | "colorchoice", 418 | "is_terminal_polyfill", 419 | "utf8parse", 420 | ] 421 | 422 | [[package]] 423 | name = "anstyle" 424 | version = "1.0.8" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 427 | 428 | [[package]] 429 | name = "anstyle-parse" 430 | version = "0.2.5" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 433 | dependencies = [ 434 | "utf8parse", 435 | ] 436 | 437 | [[package]] 438 | name = "anstyle-query" 439 | version = "1.1.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 442 | dependencies = [ 443 | "windows-sys 0.52.0", 444 | ] 445 | 446 | [[package]] 447 | name = "anstyle-wincon" 448 | version = "3.0.4" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 451 | dependencies = [ 452 | "anstyle", 453 | "windows-sys 0.52.0", 454 | ] 455 | 456 | [[package]] 457 | name = "apache-avro" 458 | version = "0.18.0" 459 | source = "git+https://github.com/apache/avro#cdfda473075273f348399902d35680590596be53" 460 | dependencies = [ 461 | "bigdecimal", 462 | "digest", 463 | "libflate", 464 | "log", 465 | "num-bigint", 466 | "quad-rand", 467 | "rand", 468 | "regex-lite", 469 | "serde", 470 | "serde_bytes", 471 | "serde_json", 472 | "strum", 473 | "strum_macros", 474 | "thiserror 1.0.68", 475 | "typed-builder", 476 | "uuid", 477 | ] 478 | 479 | [[package]] 480 | name = "autocfg" 481 | version = "1.3.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 484 | 485 | [[package]] 486 | name = "avro-schema-registry" 487 | version = "0.1.4" 488 | dependencies = [ 489 | "actix", 490 | "actix-rt", 491 | "actix-test", 492 | "actix-threadpool", 493 | "actix-web", 494 | "actix-web-prom", 495 | "apache-avro", 496 | "awc", 497 | "base64", 498 | "chrono", 499 | "diesel", 500 | "env_logger", 501 | "futures", 502 | "log", 503 | "regex", 504 | "sentry", 505 | "serde", 506 | "serde_derive", 507 | "serde_json", 508 | "sha2", 509 | "thiserror 2.0.11", 510 | ] 511 | 512 | [[package]] 513 | name = "awc" 514 | version = "3.5.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "79049b2461279b886e46f1107efc347ebecc7b88d74d023dda010551a124967b" 517 | dependencies = [ 518 | "actix-codec", 519 | "actix-http", 520 | "actix-rt", 521 | "actix-service", 522 | "actix-tls", 523 | "actix-utils", 524 | "base64", 525 | "bytes", 526 | "cfg-if", 527 | "cookie", 528 | "derive_more", 529 | "futures-core", 530 | "futures-util", 531 | "h2", 532 | "http 0.2.12", 533 | "itoa", 534 | "log", 535 | "mime", 536 | "percent-encoding", 537 | "pin-project-lite", 538 | "rand", 539 | "serde", 540 | "serde_json", 541 | "serde_urlencoded", 542 | "tokio", 543 | ] 544 | 545 | [[package]] 546 | name = "backtrace" 547 | version = "0.3.73" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 550 | dependencies = [ 551 | "addr2line", 552 | "cc", 553 | "cfg-if", 554 | "libc", 555 | "miniz_oxide 0.7.4", 556 | "object", 557 | "rustc-demangle", 558 | ] 559 | 560 | [[package]] 561 | name = "base64" 562 | version = "0.22.1" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 565 | 566 | [[package]] 567 | name = "bigdecimal" 568 | version = "0.4.5" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" 571 | dependencies = [ 572 | "autocfg", 573 | "libm", 574 | "num-bigint", 575 | "num-integer", 576 | "num-traits", 577 | "serde", 578 | ] 579 | 580 | [[package]] 581 | name = "bitflags" 582 | version = "1.3.2" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 585 | 586 | [[package]] 587 | name = "bitflags" 588 | version = "2.6.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 591 | 592 | [[package]] 593 | name = "block-buffer" 594 | version = "0.10.4" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 597 | dependencies = [ 598 | "generic-array", 599 | ] 600 | 601 | [[package]] 602 | name = "brotli" 603 | version = "6.0.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" 606 | dependencies = [ 607 | "alloc-no-stdlib", 608 | "alloc-stdlib", 609 | "brotli-decompressor", 610 | ] 611 | 612 | [[package]] 613 | name = "brotli-decompressor" 614 | version = "4.0.1" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" 617 | dependencies = [ 618 | "alloc-no-stdlib", 619 | "alloc-stdlib", 620 | ] 621 | 622 | [[package]] 623 | name = "bumpalo" 624 | version = "3.16.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 627 | 628 | [[package]] 629 | name = "byteorder" 630 | version = "1.5.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 633 | 634 | [[package]] 635 | name = "bytes" 636 | version = "1.7.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 639 | 640 | [[package]] 641 | name = "bytestring" 642 | version = "1.3.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" 645 | dependencies = [ 646 | "bytes", 647 | ] 648 | 649 | [[package]] 650 | name = "cc" 651 | version = "1.1.14" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" 654 | dependencies = [ 655 | "jobserver", 656 | "libc", 657 | "shlex", 658 | ] 659 | 660 | [[package]] 661 | name = "cfg-if" 662 | version = "1.0.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 665 | 666 | [[package]] 667 | name = "chrono" 668 | version = "0.4.39" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 671 | dependencies = [ 672 | "android-tzdata", 673 | "iana-time-zone", 674 | "js-sys", 675 | "num-traits", 676 | "serde", 677 | "wasm-bindgen", 678 | "windows-targets", 679 | ] 680 | 681 | [[package]] 682 | name = "colorchoice" 683 | version = "1.0.2" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 686 | 687 | [[package]] 688 | name = "convert_case" 689 | version = "0.4.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 692 | 693 | [[package]] 694 | name = "cookie" 695 | version = "0.16.2" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" 698 | dependencies = [ 699 | "percent-encoding", 700 | "time", 701 | "version_check", 702 | ] 703 | 704 | [[package]] 705 | name = "core-foundation" 706 | version = "0.9.4" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 709 | dependencies = [ 710 | "core-foundation-sys", 711 | "libc", 712 | ] 713 | 714 | [[package]] 715 | name = "core-foundation-sys" 716 | version = "0.8.7" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 719 | 720 | [[package]] 721 | name = "core2" 722 | version = "0.4.0" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 725 | dependencies = [ 726 | "memchr", 727 | ] 728 | 729 | [[package]] 730 | name = "cpufeatures" 731 | version = "0.2.13" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" 734 | dependencies = [ 735 | "libc", 736 | ] 737 | 738 | [[package]] 739 | name = "crc32fast" 740 | version = "1.4.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 743 | dependencies = [ 744 | "cfg-if", 745 | ] 746 | 747 | [[package]] 748 | name = "crossbeam-channel" 749 | version = "0.5.13" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 752 | dependencies = [ 753 | "crossbeam-utils", 754 | ] 755 | 756 | [[package]] 757 | name = "crossbeam-utils" 758 | version = "0.8.20" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 761 | 762 | [[package]] 763 | name = "crypto-common" 764 | version = "0.1.6" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 767 | dependencies = [ 768 | "generic-array", 769 | "typenum", 770 | ] 771 | 772 | [[package]] 773 | name = "darling" 774 | version = "0.20.10" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 777 | dependencies = [ 778 | "darling_core", 779 | "darling_macro", 780 | ] 781 | 782 | [[package]] 783 | name = "darling_core" 784 | version = "0.20.10" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 787 | dependencies = [ 788 | "fnv", 789 | "ident_case", 790 | "proc-macro2", 791 | "quote", 792 | "strsim", 793 | "syn", 794 | ] 795 | 796 | [[package]] 797 | name = "darling_macro" 798 | version = "0.20.10" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 801 | dependencies = [ 802 | "darling_core", 803 | "quote", 804 | "syn", 805 | ] 806 | 807 | [[package]] 808 | name = "dary_heap" 809 | version = "0.3.6" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" 812 | 813 | [[package]] 814 | name = "debugid" 815 | version = "0.8.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" 818 | dependencies = [ 819 | "serde", 820 | "uuid", 821 | ] 822 | 823 | [[package]] 824 | name = "deranged" 825 | version = "0.3.11" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 828 | dependencies = [ 829 | "powerfmt", 830 | ] 831 | 832 | [[package]] 833 | name = "derive_more" 834 | version = "0.99.18" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" 837 | dependencies = [ 838 | "convert_case", 839 | "proc-macro2", 840 | "quote", 841 | "rustc_version", 842 | "syn", 843 | ] 844 | 845 | [[package]] 846 | name = "diesel" 847 | version = "2.2.7" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "04001f23ba8843dc315804fa324000376084dfb1c30794ff68dd279e6e5696d5" 850 | dependencies = [ 851 | "bitflags 2.6.0", 852 | "byteorder", 853 | "chrono", 854 | "diesel_derives", 855 | "itoa", 856 | "pq-sys", 857 | "r2d2", 858 | ] 859 | 860 | [[package]] 861 | name = "diesel_derives" 862 | version = "2.2.3" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" 865 | dependencies = [ 866 | "diesel_table_macro_syntax", 867 | "dsl_auto_type", 868 | "proc-macro2", 869 | "quote", 870 | "syn", 871 | ] 872 | 873 | [[package]] 874 | name = "diesel_table_macro_syntax" 875 | version = "0.2.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" 878 | dependencies = [ 879 | "syn", 880 | ] 881 | 882 | [[package]] 883 | name = "digest" 884 | version = "0.10.7" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 887 | dependencies = [ 888 | "block-buffer", 889 | "crypto-common", 890 | ] 891 | 892 | [[package]] 893 | name = "dsl_auto_type" 894 | version = "0.1.2" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" 897 | dependencies = [ 898 | "darling", 899 | "either", 900 | "heck", 901 | "proc-macro2", 902 | "quote", 903 | "syn", 904 | ] 905 | 906 | [[package]] 907 | name = "either" 908 | version = "1.13.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 911 | 912 | [[package]] 913 | name = "encoding_rs" 914 | version = "0.8.34" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 917 | dependencies = [ 918 | "cfg-if", 919 | ] 920 | 921 | [[package]] 922 | name = "env_filter" 923 | version = "0.1.2" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 926 | dependencies = [ 927 | "log", 928 | "regex", 929 | ] 930 | 931 | [[package]] 932 | name = "env_logger" 933 | version = "0.11.6" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 936 | dependencies = [ 937 | "anstream", 938 | "anstyle", 939 | "env_filter", 940 | "humantime", 941 | "log", 942 | ] 943 | 944 | [[package]] 945 | name = "equivalent" 946 | version = "1.0.1" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 949 | 950 | [[package]] 951 | name = "errno" 952 | version = "0.3.9" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 955 | dependencies = [ 956 | "libc", 957 | "windows-sys 0.52.0", 958 | ] 959 | 960 | [[package]] 961 | name = "fastrand" 962 | version = "2.1.1" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 965 | 966 | [[package]] 967 | name = "findshlibs" 968 | version = "0.10.2" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" 971 | dependencies = [ 972 | "cc", 973 | "lazy_static", 974 | "libc", 975 | "winapi", 976 | ] 977 | 978 | [[package]] 979 | name = "flate2" 980 | version = "1.0.32" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" 983 | dependencies = [ 984 | "crc32fast", 985 | "miniz_oxide 0.8.0", 986 | ] 987 | 988 | [[package]] 989 | name = "fnv" 990 | version = "1.0.7" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 993 | 994 | [[package]] 995 | name = "foreign-types" 996 | version = "0.3.2" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 999 | dependencies = [ 1000 | "foreign-types-shared", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "foreign-types-shared" 1005 | version = "0.1.1" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 1008 | 1009 | [[package]] 1010 | name = "form_urlencoded" 1011 | version = "1.2.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 1014 | dependencies = [ 1015 | "percent-encoding", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "futures" 1020 | version = "0.3.31" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 1023 | dependencies = [ 1024 | "futures-channel", 1025 | "futures-core", 1026 | "futures-executor", 1027 | "futures-io", 1028 | "futures-sink", 1029 | "futures-task", 1030 | "futures-util", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "futures-channel" 1035 | version = "0.3.31" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 1038 | dependencies = [ 1039 | "futures-core", 1040 | "futures-sink", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "futures-core" 1045 | version = "0.3.31" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 1048 | 1049 | [[package]] 1050 | name = "futures-executor" 1051 | version = "0.3.31" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 1054 | dependencies = [ 1055 | "futures-core", 1056 | "futures-task", 1057 | "futures-util", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "futures-io" 1062 | version = "0.3.31" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 1065 | 1066 | [[package]] 1067 | name = "futures-macro" 1068 | version = "0.3.31" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1071 | dependencies = [ 1072 | "proc-macro2", 1073 | "quote", 1074 | "syn", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "futures-sink" 1079 | version = "0.3.31" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1082 | 1083 | [[package]] 1084 | name = "futures-task" 1085 | version = "0.3.31" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1088 | 1089 | [[package]] 1090 | name = "futures-util" 1091 | version = "0.3.31" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 1094 | dependencies = [ 1095 | "futures-channel", 1096 | "futures-core", 1097 | "futures-io", 1098 | "futures-macro", 1099 | "futures-sink", 1100 | "futures-task", 1101 | "memchr", 1102 | "pin-project-lite", 1103 | "pin-utils", 1104 | "slab", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "generic-array" 1109 | version = "0.14.7" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1112 | dependencies = [ 1113 | "typenum", 1114 | "version_check", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "getrandom" 1119 | version = "0.2.15" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 1122 | dependencies = [ 1123 | "cfg-if", 1124 | "libc", 1125 | "wasi", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "gimli" 1130 | version = "0.29.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 1133 | 1134 | [[package]] 1135 | name = "h2" 1136 | version = "0.3.26" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 1139 | dependencies = [ 1140 | "bytes", 1141 | "fnv", 1142 | "futures-core", 1143 | "futures-sink", 1144 | "futures-util", 1145 | "http 0.2.12", 1146 | "indexmap", 1147 | "slab", 1148 | "tokio", 1149 | "tokio-util", 1150 | "tracing", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "hashbrown" 1155 | version = "0.14.5" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1158 | dependencies = [ 1159 | "ahash", 1160 | "allocator-api2", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "heck" 1165 | version = "0.5.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1168 | 1169 | [[package]] 1170 | name = "hermit-abi" 1171 | version = "0.3.9" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 1174 | 1175 | [[package]] 1176 | name = "hex" 1177 | version = "0.4.3" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1180 | 1181 | [[package]] 1182 | name = "hostname" 1183 | version = "0.4.0" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" 1186 | dependencies = [ 1187 | "cfg-if", 1188 | "libc", 1189 | "windows", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "http" 1194 | version = "0.2.12" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 1197 | dependencies = [ 1198 | "bytes", 1199 | "fnv", 1200 | "itoa", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "http" 1205 | version = "1.1.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 1208 | dependencies = [ 1209 | "bytes", 1210 | "fnv", 1211 | "itoa", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "http-body" 1216 | version = "1.0.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1219 | dependencies = [ 1220 | "bytes", 1221 | "http 1.1.0", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "http-body-util" 1226 | version = "0.1.2" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 1229 | dependencies = [ 1230 | "bytes", 1231 | "futures-util", 1232 | "http 1.1.0", 1233 | "http-body", 1234 | "pin-project-lite", 1235 | ] 1236 | 1237 | [[package]] 1238 | name = "httparse" 1239 | version = "1.9.4" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 1242 | 1243 | [[package]] 1244 | name = "httpdate" 1245 | version = "1.0.3" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1248 | 1249 | [[package]] 1250 | name = "humantime" 1251 | version = "2.1.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 1254 | 1255 | [[package]] 1256 | name = "hyper" 1257 | version = "1.4.1" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 1260 | dependencies = [ 1261 | "bytes", 1262 | "futures-channel", 1263 | "futures-util", 1264 | "http 1.1.0", 1265 | "http-body", 1266 | "httparse", 1267 | "itoa", 1268 | "pin-project-lite", 1269 | "smallvec", 1270 | "tokio", 1271 | "want", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "hyper-tls" 1276 | version = "0.6.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1279 | dependencies = [ 1280 | "bytes", 1281 | "http-body-util", 1282 | "hyper", 1283 | "hyper-util", 1284 | "native-tls", 1285 | "tokio", 1286 | "tokio-native-tls", 1287 | "tower-service", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "hyper-util" 1292 | version = "0.1.7" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" 1295 | dependencies = [ 1296 | "bytes", 1297 | "futures-channel", 1298 | "futures-util", 1299 | "http 1.1.0", 1300 | "http-body", 1301 | "hyper", 1302 | "pin-project-lite", 1303 | "socket2", 1304 | "tokio", 1305 | "tower", 1306 | "tower-service", 1307 | "tracing", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "iana-time-zone" 1312 | version = "0.1.60" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 1315 | dependencies = [ 1316 | "android_system_properties", 1317 | "core-foundation-sys", 1318 | "iana-time-zone-haiku", 1319 | "js-sys", 1320 | "wasm-bindgen", 1321 | "windows-core", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "iana-time-zone-haiku" 1326 | version = "0.1.2" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1329 | dependencies = [ 1330 | "cc", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "ident_case" 1335 | version = "1.0.1" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1338 | 1339 | [[package]] 1340 | name = "idna" 1341 | version = "0.5.0" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 1344 | dependencies = [ 1345 | "unicode-bidi", 1346 | "unicode-normalization", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "impl-more" 1351 | version = "0.1.6" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" 1354 | 1355 | [[package]] 1356 | name = "indexmap" 1357 | version = "2.4.0" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" 1360 | dependencies = [ 1361 | "equivalent", 1362 | "hashbrown", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "instant" 1367 | version = "0.1.13" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 1370 | dependencies = [ 1371 | "cfg-if", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "ipnet" 1376 | version = "2.9.0" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 1379 | 1380 | [[package]] 1381 | name = "is_terminal_polyfill" 1382 | version = "1.70.1" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1385 | 1386 | [[package]] 1387 | name = "itoa" 1388 | version = "1.0.11" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 1391 | 1392 | [[package]] 1393 | name = "jobserver" 1394 | version = "0.1.32" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 1397 | dependencies = [ 1398 | "libc", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "js-sys" 1403 | version = "0.3.70" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 1406 | dependencies = [ 1407 | "wasm-bindgen", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "language-tags" 1412 | version = "0.3.2" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1415 | 1416 | [[package]] 1417 | name = "lazy_static" 1418 | version = "1.5.0" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1421 | 1422 | [[package]] 1423 | name = "libc" 1424 | version = "0.2.158" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 1427 | 1428 | [[package]] 1429 | name = "libflate" 1430 | version = "2.1.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" 1433 | dependencies = [ 1434 | "adler32", 1435 | "core2", 1436 | "crc32fast", 1437 | "dary_heap", 1438 | "libflate_lz77", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "libflate_lz77" 1443 | version = "2.1.0" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" 1446 | dependencies = [ 1447 | "core2", 1448 | "hashbrown", 1449 | "rle-decode-fast", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "libm" 1454 | version = "0.2.8" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 1457 | 1458 | [[package]] 1459 | name = "linux-raw-sys" 1460 | version = "0.4.14" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1463 | 1464 | [[package]] 1465 | name = "local-channel" 1466 | version = "0.1.5" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" 1469 | dependencies = [ 1470 | "futures-core", 1471 | "futures-sink", 1472 | "local-waker", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "local-waker" 1477 | version = "0.1.4" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" 1480 | 1481 | [[package]] 1482 | name = "lock_api" 1483 | version = "0.4.12" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1486 | dependencies = [ 1487 | "autocfg", 1488 | "scopeguard", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "log" 1493 | version = "0.4.25" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 1496 | 1497 | [[package]] 1498 | name = "memchr" 1499 | version = "2.7.4" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1502 | 1503 | [[package]] 1504 | name = "mime" 1505 | version = "0.3.17" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1508 | 1509 | [[package]] 1510 | name = "miniz_oxide" 1511 | version = "0.7.4" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 1514 | dependencies = [ 1515 | "adler", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "miniz_oxide" 1520 | version = "0.8.0" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 1523 | dependencies = [ 1524 | "adler2", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "mio" 1529 | version = "1.0.2" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 1532 | dependencies = [ 1533 | "hermit-abi", 1534 | "libc", 1535 | "log", 1536 | "wasi", 1537 | "windows-sys 0.52.0", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "native-tls" 1542 | version = "0.2.12" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 1545 | dependencies = [ 1546 | "libc", 1547 | "log", 1548 | "openssl", 1549 | "openssl-probe", 1550 | "openssl-sys", 1551 | "schannel", 1552 | "security-framework", 1553 | "security-framework-sys", 1554 | "tempfile", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "num-bigint" 1559 | version = "0.4.6" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1562 | dependencies = [ 1563 | "num-integer", 1564 | "num-traits", 1565 | "serde", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "num-conv" 1570 | version = "0.1.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1573 | 1574 | [[package]] 1575 | name = "num-integer" 1576 | version = "0.1.46" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1579 | dependencies = [ 1580 | "num-traits", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "num-traits" 1585 | version = "0.2.19" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1588 | dependencies = [ 1589 | "autocfg", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "num_cpus" 1594 | version = "1.16.0" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1597 | dependencies = [ 1598 | "hermit-abi", 1599 | "libc", 1600 | ] 1601 | 1602 | [[package]] 1603 | name = "object" 1604 | version = "0.36.3" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" 1607 | dependencies = [ 1608 | "memchr", 1609 | ] 1610 | 1611 | [[package]] 1612 | name = "once_cell" 1613 | version = "1.19.0" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1616 | 1617 | [[package]] 1618 | name = "openssl" 1619 | version = "0.10.66" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 1622 | dependencies = [ 1623 | "bitflags 2.6.0", 1624 | "cfg-if", 1625 | "foreign-types", 1626 | "libc", 1627 | "once_cell", 1628 | "openssl-macros", 1629 | "openssl-sys", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "openssl-macros" 1634 | version = "0.1.1" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1637 | dependencies = [ 1638 | "proc-macro2", 1639 | "quote", 1640 | "syn", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "openssl-probe" 1645 | version = "0.1.5" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1648 | 1649 | [[package]] 1650 | name = "openssl-sys" 1651 | version = "0.9.103" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 1654 | dependencies = [ 1655 | "cc", 1656 | "libc", 1657 | "pkg-config", 1658 | "vcpkg", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "os_info" 1663 | version = "3.8.2" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" 1666 | dependencies = [ 1667 | "log", 1668 | "serde", 1669 | "windows-sys 0.52.0", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "parking_lot" 1674 | version = "0.11.2" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1677 | dependencies = [ 1678 | "instant", 1679 | "lock_api", 1680 | "parking_lot_core 0.8.6", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "parking_lot" 1685 | version = "0.12.3" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1688 | dependencies = [ 1689 | "lock_api", 1690 | "parking_lot_core 0.9.10", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "parking_lot_core" 1695 | version = "0.8.6" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 1698 | dependencies = [ 1699 | "cfg-if", 1700 | "instant", 1701 | "libc", 1702 | "redox_syscall 0.2.16", 1703 | "smallvec", 1704 | "winapi", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "parking_lot_core" 1709 | version = "0.9.10" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1712 | dependencies = [ 1713 | "cfg-if", 1714 | "libc", 1715 | "redox_syscall 0.5.3", 1716 | "smallvec", 1717 | "windows-targets", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "paste" 1722 | version = "1.0.15" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1725 | 1726 | [[package]] 1727 | name = "percent-encoding" 1728 | version = "2.3.1" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1731 | 1732 | [[package]] 1733 | name = "pin-project" 1734 | version = "1.1.5" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1737 | dependencies = [ 1738 | "pin-project-internal", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "pin-project-internal" 1743 | version = "1.1.5" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1746 | dependencies = [ 1747 | "proc-macro2", 1748 | "quote", 1749 | "syn", 1750 | ] 1751 | 1752 | [[package]] 1753 | name = "pin-project-lite" 1754 | version = "0.2.14" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1757 | 1758 | [[package]] 1759 | name = "pin-utils" 1760 | version = "0.1.0" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1763 | 1764 | [[package]] 1765 | name = "pkg-config" 1766 | version = "0.3.30" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1769 | 1770 | [[package]] 1771 | name = "powerfmt" 1772 | version = "0.2.0" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1775 | 1776 | [[package]] 1777 | name = "ppv-lite86" 1778 | version = "0.2.20" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1781 | dependencies = [ 1782 | "zerocopy", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "pq-sys" 1787 | version = "0.6.1" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "a24ff9e4cf6945c988f0db7005d87747bf72864965c3529d259ad155ac41d584" 1790 | dependencies = [ 1791 | "vcpkg", 1792 | ] 1793 | 1794 | [[package]] 1795 | name = "proc-macro2" 1796 | version = "1.0.86" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1799 | dependencies = [ 1800 | "unicode-ident", 1801 | ] 1802 | 1803 | [[package]] 1804 | name = "prometheus" 1805 | version = "0.13.4" 1806 | source = "registry+https://github.com/rust-lang/crates.io-index" 1807 | checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" 1808 | dependencies = [ 1809 | "cfg-if", 1810 | "fnv", 1811 | "lazy_static", 1812 | "memchr", 1813 | "parking_lot 0.12.3", 1814 | "thiserror 1.0.68", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "quad-rand" 1819 | version = "0.2.1" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "658fa1faf7a4cc5f057c9ee5ef560f717ad9d8dc66d975267f709624d6e1ab88" 1822 | 1823 | [[package]] 1824 | name = "quote" 1825 | version = "1.0.37" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1828 | dependencies = [ 1829 | "proc-macro2", 1830 | ] 1831 | 1832 | [[package]] 1833 | name = "r2d2" 1834 | version = "0.8.10" 1835 | source = "registry+https://github.com/rust-lang/crates.io-index" 1836 | checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" 1837 | dependencies = [ 1838 | "log", 1839 | "parking_lot 0.12.3", 1840 | "scheduled-thread-pool", 1841 | ] 1842 | 1843 | [[package]] 1844 | name = "rand" 1845 | version = "0.8.5" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1848 | dependencies = [ 1849 | "libc", 1850 | "rand_chacha", 1851 | "rand_core", 1852 | ] 1853 | 1854 | [[package]] 1855 | name = "rand_chacha" 1856 | version = "0.3.1" 1857 | source = "registry+https://github.com/rust-lang/crates.io-index" 1858 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1859 | dependencies = [ 1860 | "ppv-lite86", 1861 | "rand_core", 1862 | ] 1863 | 1864 | [[package]] 1865 | name = "rand_core" 1866 | version = "0.6.4" 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" 1868 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1869 | dependencies = [ 1870 | "getrandom", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "redox_syscall" 1875 | version = "0.2.16" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1878 | dependencies = [ 1879 | "bitflags 1.3.2", 1880 | ] 1881 | 1882 | [[package]] 1883 | name = "redox_syscall" 1884 | version = "0.5.3" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 1887 | dependencies = [ 1888 | "bitflags 2.6.0", 1889 | ] 1890 | 1891 | [[package]] 1892 | name = "regex" 1893 | version = "1.11.1" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1896 | dependencies = [ 1897 | "aho-corasick", 1898 | "memchr", 1899 | "regex-automata", 1900 | "regex-syntax", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "regex-automata" 1905 | version = "0.4.8" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1908 | dependencies = [ 1909 | "aho-corasick", 1910 | "memchr", 1911 | "regex-syntax", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "regex-lite" 1916 | version = "0.1.6" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 1919 | 1920 | [[package]] 1921 | name = "regex-syntax" 1922 | version = "0.8.5" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1925 | 1926 | [[package]] 1927 | name = "reqwest" 1928 | version = "0.12.7" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" 1931 | dependencies = [ 1932 | "base64", 1933 | "bytes", 1934 | "futures-channel", 1935 | "futures-core", 1936 | "futures-util", 1937 | "http 1.1.0", 1938 | "http-body", 1939 | "http-body-util", 1940 | "hyper", 1941 | "hyper-tls", 1942 | "hyper-util", 1943 | "ipnet", 1944 | "js-sys", 1945 | "log", 1946 | "mime", 1947 | "native-tls", 1948 | "once_cell", 1949 | "percent-encoding", 1950 | "pin-project-lite", 1951 | "rustls-pemfile", 1952 | "serde", 1953 | "serde_json", 1954 | "serde_urlencoded", 1955 | "sync_wrapper", 1956 | "tokio", 1957 | "tokio-native-tls", 1958 | "tower-service", 1959 | "url", 1960 | "wasm-bindgen", 1961 | "wasm-bindgen-futures", 1962 | "web-sys", 1963 | "windows-registry", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "rle-decode-fast" 1968 | version = "1.0.3" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 1971 | 1972 | [[package]] 1973 | name = "rustc-demangle" 1974 | version = "0.1.24" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1977 | 1978 | [[package]] 1979 | name = "rustc_version" 1980 | version = "0.4.0" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1983 | dependencies = [ 1984 | "semver", 1985 | ] 1986 | 1987 | [[package]] 1988 | name = "rustix" 1989 | version = "0.38.34" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1992 | dependencies = [ 1993 | "bitflags 2.6.0", 1994 | "errno", 1995 | "libc", 1996 | "linux-raw-sys", 1997 | "windows-sys 0.52.0", 1998 | ] 1999 | 2000 | [[package]] 2001 | name = "rustls-pemfile" 2002 | version = "2.1.3" 2003 | source = "registry+https://github.com/rust-lang/crates.io-index" 2004 | checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" 2005 | dependencies = [ 2006 | "base64", 2007 | "rustls-pki-types", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "rustls-pki-types" 2012 | version = "1.8.0" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" 2015 | 2016 | [[package]] 2017 | name = "rustversion" 2018 | version = "1.0.17" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 2021 | 2022 | [[package]] 2023 | name = "ryu" 2024 | version = "1.0.18" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 2027 | 2028 | [[package]] 2029 | name = "schannel" 2030 | version = "0.1.23" 2031 | source = "registry+https://github.com/rust-lang/crates.io-index" 2032 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 2033 | dependencies = [ 2034 | "windows-sys 0.52.0", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "scheduled-thread-pool" 2039 | version = "0.2.7" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" 2042 | dependencies = [ 2043 | "parking_lot 0.12.3", 2044 | ] 2045 | 2046 | [[package]] 2047 | name = "scopeguard" 2048 | version = "1.2.0" 2049 | source = "registry+https://github.com/rust-lang/crates.io-index" 2050 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2051 | 2052 | [[package]] 2053 | name = "security-framework" 2054 | version = "2.11.1" 2055 | source = "registry+https://github.com/rust-lang/crates.io-index" 2056 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2057 | dependencies = [ 2058 | "bitflags 2.6.0", 2059 | "core-foundation", 2060 | "core-foundation-sys", 2061 | "libc", 2062 | "security-framework-sys", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "security-framework-sys" 2067 | version = "2.11.1" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" 2070 | dependencies = [ 2071 | "core-foundation-sys", 2072 | "libc", 2073 | ] 2074 | 2075 | [[package]] 2076 | name = "semver" 2077 | version = "1.0.23" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 2080 | 2081 | [[package]] 2082 | name = "sentry" 2083 | version = "0.36.0" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "3a7332159e544e34db06b251b1eda5e546bd90285c3f58d9c8ff8450b484e0da" 2086 | dependencies = [ 2087 | "httpdate", 2088 | "native-tls", 2089 | "reqwest", 2090 | "sentry-backtrace", 2091 | "sentry-contexts", 2092 | "sentry-core", 2093 | "sentry-debug-images", 2094 | "sentry-panic", 2095 | "sentry-tracing", 2096 | "tokio", 2097 | "ureq", 2098 | ] 2099 | 2100 | [[package]] 2101 | name = "sentry-backtrace" 2102 | version = "0.36.0" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "565ec31ad37bab8e6d9f289f34913ed8768347b133706192f10606dabd5c6bc4" 2105 | dependencies = [ 2106 | "backtrace", 2107 | "once_cell", 2108 | "regex", 2109 | "sentry-core", 2110 | ] 2111 | 2112 | [[package]] 2113 | name = "sentry-contexts" 2114 | version = "0.36.0" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "e860275f25f27e8c0c7726ce116c7d5c928c5bba2ee73306e52b20a752298ea6" 2117 | dependencies = [ 2118 | "hostname", 2119 | "libc", 2120 | "os_info", 2121 | "rustc_version", 2122 | "sentry-core", 2123 | "uname", 2124 | ] 2125 | 2126 | [[package]] 2127 | name = "sentry-core" 2128 | version = "0.36.0" 2129 | source = "registry+https://github.com/rust-lang/crates.io-index" 2130 | checksum = "653942e6141f16651273159f4b8b1eaeedf37a7554c00cd798953e64b8a9bf72" 2131 | dependencies = [ 2132 | "once_cell", 2133 | "rand", 2134 | "sentry-types", 2135 | "serde", 2136 | "serde_json", 2137 | ] 2138 | 2139 | [[package]] 2140 | name = "sentry-debug-images" 2141 | version = "0.36.0" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "2a60bc2154e6df59beed0ac13d58f8dfaf5ad20a88548a53e29e4d92e8e835c2" 2144 | dependencies = [ 2145 | "findshlibs", 2146 | "once_cell", 2147 | "sentry-core", 2148 | ] 2149 | 2150 | [[package]] 2151 | name = "sentry-panic" 2152 | version = "0.36.0" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "105e3a956c8aa9dab1e4087b1657b03271bfc49d838c6ae9bfc7c58c802fd0ef" 2155 | dependencies = [ 2156 | "sentry-backtrace", 2157 | "sentry-core", 2158 | ] 2159 | 2160 | [[package]] 2161 | name = "sentry-tracing" 2162 | version = "0.36.0" 2163 | source = "registry+https://github.com/rust-lang/crates.io-index" 2164 | checksum = "64e75c831b4d8b34a5aec1f65f67c5d46a26c7c5d3c7abd8b5ef430796900cf8" 2165 | dependencies = [ 2166 | "sentry-backtrace", 2167 | "sentry-core", 2168 | "tracing-core", 2169 | "tracing-subscriber", 2170 | ] 2171 | 2172 | [[package]] 2173 | name = "sentry-types" 2174 | version = "0.36.0" 2175 | source = "registry+https://github.com/rust-lang/crates.io-index" 2176 | checksum = "2d4203359e60724aa05cf2385aaf5d4f147e837185d7dd2b9ccf1ee77f4420c8" 2177 | dependencies = [ 2178 | "debugid", 2179 | "hex", 2180 | "rand", 2181 | "serde", 2182 | "serde_json", 2183 | "thiserror 1.0.68", 2184 | "time", 2185 | "url", 2186 | "uuid", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "serde" 2191 | version = "1.0.217" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 2194 | dependencies = [ 2195 | "serde_derive", 2196 | ] 2197 | 2198 | [[package]] 2199 | name = "serde_bytes" 2200 | version = "0.11.15" 2201 | source = "registry+https://github.com/rust-lang/crates.io-index" 2202 | checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" 2203 | dependencies = [ 2204 | "serde", 2205 | ] 2206 | 2207 | [[package]] 2208 | name = "serde_derive" 2209 | version = "1.0.217" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 2212 | dependencies = [ 2213 | "proc-macro2", 2214 | "quote", 2215 | "syn", 2216 | ] 2217 | 2218 | [[package]] 2219 | name = "serde_json" 2220 | version = "1.0.138" 2221 | source = "registry+https://github.com/rust-lang/crates.io-index" 2222 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 2223 | dependencies = [ 2224 | "itoa", 2225 | "memchr", 2226 | "ryu", 2227 | "serde", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "serde_urlencoded" 2232 | version = "0.7.1" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2235 | dependencies = [ 2236 | "form_urlencoded", 2237 | "itoa", 2238 | "ryu", 2239 | "serde", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "sha1" 2244 | version = "0.10.6" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2247 | dependencies = [ 2248 | "cfg-if", 2249 | "cpufeatures", 2250 | "digest", 2251 | ] 2252 | 2253 | [[package]] 2254 | name = "sha2" 2255 | version = "0.10.8" 2256 | source = "registry+https://github.com/rust-lang/crates.io-index" 2257 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 2258 | dependencies = [ 2259 | "cfg-if", 2260 | "cpufeatures", 2261 | "digest", 2262 | ] 2263 | 2264 | [[package]] 2265 | name = "shlex" 2266 | version = "1.3.0" 2267 | source = "registry+https://github.com/rust-lang/crates.io-index" 2268 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2269 | 2270 | [[package]] 2271 | name = "signal-hook-registry" 2272 | version = "1.4.2" 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" 2274 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 2275 | dependencies = [ 2276 | "libc", 2277 | ] 2278 | 2279 | [[package]] 2280 | name = "slab" 2281 | version = "0.4.9" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2284 | dependencies = [ 2285 | "autocfg", 2286 | ] 2287 | 2288 | [[package]] 2289 | name = "smallvec" 2290 | version = "1.13.2" 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" 2292 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 2293 | 2294 | [[package]] 2295 | name = "socket2" 2296 | version = "0.5.7" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 2299 | dependencies = [ 2300 | "libc", 2301 | "windows-sys 0.52.0", 2302 | ] 2303 | 2304 | [[package]] 2305 | name = "strfmt" 2306 | version = "0.2.4" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" 2309 | 2310 | [[package]] 2311 | name = "strsim" 2312 | version = "0.11.1" 2313 | source = "registry+https://github.com/rust-lang/crates.io-index" 2314 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2315 | 2316 | [[package]] 2317 | name = "strum" 2318 | version = "0.26.3" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 2321 | 2322 | [[package]] 2323 | name = "strum_macros" 2324 | version = "0.26.4" 2325 | source = "registry+https://github.com/rust-lang/crates.io-index" 2326 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 2327 | dependencies = [ 2328 | "heck", 2329 | "proc-macro2", 2330 | "quote", 2331 | "rustversion", 2332 | "syn", 2333 | ] 2334 | 2335 | [[package]] 2336 | name = "syn" 2337 | version = "2.0.87" 2338 | source = "registry+https://github.com/rust-lang/crates.io-index" 2339 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 2340 | dependencies = [ 2341 | "proc-macro2", 2342 | "quote", 2343 | "unicode-ident", 2344 | ] 2345 | 2346 | [[package]] 2347 | name = "sync_wrapper" 2348 | version = "1.0.1" 2349 | source = "registry+https://github.com/rust-lang/crates.io-index" 2350 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 2351 | dependencies = [ 2352 | "futures-core", 2353 | ] 2354 | 2355 | [[package]] 2356 | name = "tempfile" 2357 | version = "3.12.0" 2358 | source = "registry+https://github.com/rust-lang/crates.io-index" 2359 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 2360 | dependencies = [ 2361 | "cfg-if", 2362 | "fastrand", 2363 | "once_cell", 2364 | "rustix", 2365 | "windows-sys 0.59.0", 2366 | ] 2367 | 2368 | [[package]] 2369 | name = "thiserror" 2370 | version = "1.0.68" 2371 | source = "registry+https://github.com/rust-lang/crates.io-index" 2372 | checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" 2373 | dependencies = [ 2374 | "thiserror-impl 1.0.68", 2375 | ] 2376 | 2377 | [[package]] 2378 | name = "thiserror" 2379 | version = "2.0.11" 2380 | source = "registry+https://github.com/rust-lang/crates.io-index" 2381 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 2382 | dependencies = [ 2383 | "thiserror-impl 2.0.11", 2384 | ] 2385 | 2386 | [[package]] 2387 | name = "thiserror-impl" 2388 | version = "1.0.68" 2389 | source = "registry+https://github.com/rust-lang/crates.io-index" 2390 | checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" 2391 | dependencies = [ 2392 | "proc-macro2", 2393 | "quote", 2394 | "syn", 2395 | ] 2396 | 2397 | [[package]] 2398 | name = "thiserror-impl" 2399 | version = "2.0.11" 2400 | source = "registry+https://github.com/rust-lang/crates.io-index" 2401 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 2402 | dependencies = [ 2403 | "proc-macro2", 2404 | "quote", 2405 | "syn", 2406 | ] 2407 | 2408 | [[package]] 2409 | name = "threadpool" 2410 | version = "1.8.1" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 2413 | dependencies = [ 2414 | "num_cpus", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "time" 2419 | version = "0.3.36" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 2422 | dependencies = [ 2423 | "deranged", 2424 | "itoa", 2425 | "num-conv", 2426 | "powerfmt", 2427 | "serde", 2428 | "time-core", 2429 | "time-macros", 2430 | ] 2431 | 2432 | [[package]] 2433 | name = "time-core" 2434 | version = "0.1.2" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 2437 | 2438 | [[package]] 2439 | name = "time-macros" 2440 | version = "0.2.18" 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" 2442 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 2443 | dependencies = [ 2444 | "num-conv", 2445 | "time-core", 2446 | ] 2447 | 2448 | [[package]] 2449 | name = "tinyvec" 2450 | version = "1.8.0" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 2453 | dependencies = [ 2454 | "tinyvec_macros", 2455 | ] 2456 | 2457 | [[package]] 2458 | name = "tinyvec_macros" 2459 | version = "0.1.1" 2460 | source = "registry+https://github.com/rust-lang/crates.io-index" 2461 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2462 | 2463 | [[package]] 2464 | name = "tokio" 2465 | version = "1.39.3" 2466 | source = "registry+https://github.com/rust-lang/crates.io-index" 2467 | checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" 2468 | dependencies = [ 2469 | "backtrace", 2470 | "bytes", 2471 | "libc", 2472 | "mio", 2473 | "parking_lot 0.12.3", 2474 | "pin-project-lite", 2475 | "signal-hook-registry", 2476 | "socket2", 2477 | "windows-sys 0.52.0", 2478 | ] 2479 | 2480 | [[package]] 2481 | name = "tokio-native-tls" 2482 | version = "0.3.1" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2485 | dependencies = [ 2486 | "native-tls", 2487 | "tokio", 2488 | ] 2489 | 2490 | [[package]] 2491 | name = "tokio-util" 2492 | version = "0.7.11" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 2495 | dependencies = [ 2496 | "bytes", 2497 | "futures-core", 2498 | "futures-sink", 2499 | "pin-project-lite", 2500 | "tokio", 2501 | ] 2502 | 2503 | [[package]] 2504 | name = "tower" 2505 | version = "0.4.13" 2506 | source = "registry+https://github.com/rust-lang/crates.io-index" 2507 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 2508 | dependencies = [ 2509 | "futures-core", 2510 | "futures-util", 2511 | "pin-project", 2512 | "pin-project-lite", 2513 | "tokio", 2514 | "tower-layer", 2515 | "tower-service", 2516 | ] 2517 | 2518 | [[package]] 2519 | name = "tower-layer" 2520 | version = "0.3.3" 2521 | source = "registry+https://github.com/rust-lang/crates.io-index" 2522 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2523 | 2524 | [[package]] 2525 | name = "tower-service" 2526 | version = "0.3.3" 2527 | source = "registry+https://github.com/rust-lang/crates.io-index" 2528 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2529 | 2530 | [[package]] 2531 | name = "tracing" 2532 | version = "0.1.40" 2533 | source = "registry+https://github.com/rust-lang/crates.io-index" 2534 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2535 | dependencies = [ 2536 | "log", 2537 | "pin-project-lite", 2538 | "tracing-core", 2539 | ] 2540 | 2541 | [[package]] 2542 | name = "tracing-core" 2543 | version = "0.1.32" 2544 | source = "registry+https://github.com/rust-lang/crates.io-index" 2545 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2546 | dependencies = [ 2547 | "once_cell", 2548 | "valuable", 2549 | ] 2550 | 2551 | [[package]] 2552 | name = "tracing-subscriber" 2553 | version = "0.3.18" 2554 | source = "registry+https://github.com/rust-lang/crates.io-index" 2555 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 2556 | dependencies = [ 2557 | "tracing-core", 2558 | ] 2559 | 2560 | [[package]] 2561 | name = "try-lock" 2562 | version = "0.2.5" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2565 | 2566 | [[package]] 2567 | name = "typed-builder" 2568 | version = "0.20.0" 2569 | source = "registry+https://github.com/rust-lang/crates.io-index" 2570 | checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" 2571 | dependencies = [ 2572 | "typed-builder-macro", 2573 | ] 2574 | 2575 | [[package]] 2576 | name = "typed-builder-macro" 2577 | version = "0.20.0" 2578 | source = "registry+https://github.com/rust-lang/crates.io-index" 2579 | checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" 2580 | dependencies = [ 2581 | "proc-macro2", 2582 | "quote", 2583 | "syn", 2584 | ] 2585 | 2586 | [[package]] 2587 | name = "typenum" 2588 | version = "1.17.0" 2589 | source = "registry+https://github.com/rust-lang/crates.io-index" 2590 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2591 | 2592 | [[package]] 2593 | name = "uname" 2594 | version = "0.1.1" 2595 | source = "registry+https://github.com/rust-lang/crates.io-index" 2596 | checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" 2597 | dependencies = [ 2598 | "libc", 2599 | ] 2600 | 2601 | [[package]] 2602 | name = "unicode-bidi" 2603 | version = "0.3.15" 2604 | source = "registry+https://github.com/rust-lang/crates.io-index" 2605 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 2606 | 2607 | [[package]] 2608 | name = "unicode-ident" 2609 | version = "1.0.12" 2610 | source = "registry+https://github.com/rust-lang/crates.io-index" 2611 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 2612 | 2613 | [[package]] 2614 | name = "unicode-normalization" 2615 | version = "0.1.23" 2616 | source = "registry+https://github.com/rust-lang/crates.io-index" 2617 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 2618 | dependencies = [ 2619 | "tinyvec", 2620 | ] 2621 | 2622 | [[package]] 2623 | name = "ureq" 2624 | version = "2.10.1" 2625 | source = "registry+https://github.com/rust-lang/crates.io-index" 2626 | checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" 2627 | dependencies = [ 2628 | "base64", 2629 | "log", 2630 | "native-tls", 2631 | "once_cell", 2632 | "url", 2633 | ] 2634 | 2635 | [[package]] 2636 | name = "url" 2637 | version = "2.5.2" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 2640 | dependencies = [ 2641 | "form_urlencoded", 2642 | "idna", 2643 | "percent-encoding", 2644 | "serde", 2645 | ] 2646 | 2647 | [[package]] 2648 | name = "utf8parse" 2649 | version = "0.2.2" 2650 | source = "registry+https://github.com/rust-lang/crates.io-index" 2651 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2652 | 2653 | [[package]] 2654 | name = "uuid" 2655 | version = "1.10.0" 2656 | source = "registry+https://github.com/rust-lang/crates.io-index" 2657 | checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" 2658 | dependencies = [ 2659 | "serde", 2660 | ] 2661 | 2662 | [[package]] 2663 | name = "valuable" 2664 | version = "0.1.0" 2665 | source = "registry+https://github.com/rust-lang/crates.io-index" 2666 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2667 | 2668 | [[package]] 2669 | name = "vcpkg" 2670 | version = "0.2.15" 2671 | source = "registry+https://github.com/rust-lang/crates.io-index" 2672 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2673 | 2674 | [[package]] 2675 | name = "version_check" 2676 | version = "0.9.5" 2677 | source = "registry+https://github.com/rust-lang/crates.io-index" 2678 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2679 | 2680 | [[package]] 2681 | name = "want" 2682 | version = "0.3.1" 2683 | source = "registry+https://github.com/rust-lang/crates.io-index" 2684 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2685 | dependencies = [ 2686 | "try-lock", 2687 | ] 2688 | 2689 | [[package]] 2690 | name = "wasi" 2691 | version = "0.11.0+wasi-snapshot-preview1" 2692 | source = "registry+https://github.com/rust-lang/crates.io-index" 2693 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2694 | 2695 | [[package]] 2696 | name = "wasm-bindgen" 2697 | version = "0.2.93" 2698 | source = "registry+https://github.com/rust-lang/crates.io-index" 2699 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 2700 | dependencies = [ 2701 | "cfg-if", 2702 | "once_cell", 2703 | "wasm-bindgen-macro", 2704 | ] 2705 | 2706 | [[package]] 2707 | name = "wasm-bindgen-backend" 2708 | version = "0.2.93" 2709 | source = "registry+https://github.com/rust-lang/crates.io-index" 2710 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 2711 | dependencies = [ 2712 | "bumpalo", 2713 | "log", 2714 | "once_cell", 2715 | "proc-macro2", 2716 | "quote", 2717 | "syn", 2718 | "wasm-bindgen-shared", 2719 | ] 2720 | 2721 | [[package]] 2722 | name = "wasm-bindgen-futures" 2723 | version = "0.4.43" 2724 | source = "registry+https://github.com/rust-lang/crates.io-index" 2725 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 2726 | dependencies = [ 2727 | "cfg-if", 2728 | "js-sys", 2729 | "wasm-bindgen", 2730 | "web-sys", 2731 | ] 2732 | 2733 | [[package]] 2734 | name = "wasm-bindgen-macro" 2735 | version = "0.2.93" 2736 | source = "registry+https://github.com/rust-lang/crates.io-index" 2737 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 2738 | dependencies = [ 2739 | "quote", 2740 | "wasm-bindgen-macro-support", 2741 | ] 2742 | 2743 | [[package]] 2744 | name = "wasm-bindgen-macro-support" 2745 | version = "0.2.93" 2746 | source = "registry+https://github.com/rust-lang/crates.io-index" 2747 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 2748 | dependencies = [ 2749 | "proc-macro2", 2750 | "quote", 2751 | "syn", 2752 | "wasm-bindgen-backend", 2753 | "wasm-bindgen-shared", 2754 | ] 2755 | 2756 | [[package]] 2757 | name = "wasm-bindgen-shared" 2758 | version = "0.2.93" 2759 | source = "registry+https://github.com/rust-lang/crates.io-index" 2760 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 2761 | 2762 | [[package]] 2763 | name = "web-sys" 2764 | version = "0.3.70" 2765 | source = "registry+https://github.com/rust-lang/crates.io-index" 2766 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 2767 | dependencies = [ 2768 | "js-sys", 2769 | "wasm-bindgen", 2770 | ] 2771 | 2772 | [[package]] 2773 | name = "winapi" 2774 | version = "0.3.9" 2775 | source = "registry+https://github.com/rust-lang/crates.io-index" 2776 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2777 | dependencies = [ 2778 | "winapi-i686-pc-windows-gnu", 2779 | "winapi-x86_64-pc-windows-gnu", 2780 | ] 2781 | 2782 | [[package]] 2783 | name = "winapi-i686-pc-windows-gnu" 2784 | version = "0.4.0" 2785 | source = "registry+https://github.com/rust-lang/crates.io-index" 2786 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2787 | 2788 | [[package]] 2789 | name = "winapi-x86_64-pc-windows-gnu" 2790 | version = "0.4.0" 2791 | source = "registry+https://github.com/rust-lang/crates.io-index" 2792 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2793 | 2794 | [[package]] 2795 | name = "windows" 2796 | version = "0.52.0" 2797 | source = "registry+https://github.com/rust-lang/crates.io-index" 2798 | checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" 2799 | dependencies = [ 2800 | "windows-core", 2801 | "windows-targets", 2802 | ] 2803 | 2804 | [[package]] 2805 | name = "windows-core" 2806 | version = "0.52.0" 2807 | source = "registry+https://github.com/rust-lang/crates.io-index" 2808 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2809 | dependencies = [ 2810 | "windows-targets", 2811 | ] 2812 | 2813 | [[package]] 2814 | name = "windows-registry" 2815 | version = "0.2.0" 2816 | source = "registry+https://github.com/rust-lang/crates.io-index" 2817 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 2818 | dependencies = [ 2819 | "windows-result", 2820 | "windows-strings", 2821 | "windows-targets", 2822 | ] 2823 | 2824 | [[package]] 2825 | name = "windows-result" 2826 | version = "0.2.0" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 2829 | dependencies = [ 2830 | "windows-targets", 2831 | ] 2832 | 2833 | [[package]] 2834 | name = "windows-strings" 2835 | version = "0.1.0" 2836 | source = "registry+https://github.com/rust-lang/crates.io-index" 2837 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 2838 | dependencies = [ 2839 | "windows-result", 2840 | "windows-targets", 2841 | ] 2842 | 2843 | [[package]] 2844 | name = "windows-sys" 2845 | version = "0.52.0" 2846 | source = "registry+https://github.com/rust-lang/crates.io-index" 2847 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2848 | dependencies = [ 2849 | "windows-targets", 2850 | ] 2851 | 2852 | [[package]] 2853 | name = "windows-sys" 2854 | version = "0.59.0" 2855 | source = "registry+https://github.com/rust-lang/crates.io-index" 2856 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2857 | dependencies = [ 2858 | "windows-targets", 2859 | ] 2860 | 2861 | [[package]] 2862 | name = "windows-targets" 2863 | version = "0.52.6" 2864 | source = "registry+https://github.com/rust-lang/crates.io-index" 2865 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2866 | dependencies = [ 2867 | "windows_aarch64_gnullvm", 2868 | "windows_aarch64_msvc", 2869 | "windows_i686_gnu", 2870 | "windows_i686_gnullvm", 2871 | "windows_i686_msvc", 2872 | "windows_x86_64_gnu", 2873 | "windows_x86_64_gnullvm", 2874 | "windows_x86_64_msvc", 2875 | ] 2876 | 2877 | [[package]] 2878 | name = "windows_aarch64_gnullvm" 2879 | version = "0.52.6" 2880 | source = "registry+https://github.com/rust-lang/crates.io-index" 2881 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2882 | 2883 | [[package]] 2884 | name = "windows_aarch64_msvc" 2885 | version = "0.52.6" 2886 | source = "registry+https://github.com/rust-lang/crates.io-index" 2887 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2888 | 2889 | [[package]] 2890 | name = "windows_i686_gnu" 2891 | version = "0.52.6" 2892 | source = "registry+https://github.com/rust-lang/crates.io-index" 2893 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2894 | 2895 | [[package]] 2896 | name = "windows_i686_gnullvm" 2897 | version = "0.52.6" 2898 | source = "registry+https://github.com/rust-lang/crates.io-index" 2899 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2900 | 2901 | [[package]] 2902 | name = "windows_i686_msvc" 2903 | version = "0.52.6" 2904 | source = "registry+https://github.com/rust-lang/crates.io-index" 2905 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2906 | 2907 | [[package]] 2908 | name = "windows_x86_64_gnu" 2909 | version = "0.52.6" 2910 | source = "registry+https://github.com/rust-lang/crates.io-index" 2911 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2912 | 2913 | [[package]] 2914 | name = "windows_x86_64_gnullvm" 2915 | version = "0.52.6" 2916 | source = "registry+https://github.com/rust-lang/crates.io-index" 2917 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2918 | 2919 | [[package]] 2920 | name = "windows_x86_64_msvc" 2921 | version = "0.52.6" 2922 | source = "registry+https://github.com/rust-lang/crates.io-index" 2923 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2924 | 2925 | [[package]] 2926 | name = "zerocopy" 2927 | version = "0.7.35" 2928 | source = "registry+https://github.com/rust-lang/crates.io-index" 2929 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2930 | dependencies = [ 2931 | "byteorder", 2932 | "zerocopy-derive", 2933 | ] 2934 | 2935 | [[package]] 2936 | name = "zerocopy-derive" 2937 | version = "0.7.35" 2938 | source = "registry+https://github.com/rust-lang/crates.io-index" 2939 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2940 | dependencies = [ 2941 | "proc-macro2", 2942 | "quote", 2943 | "syn", 2944 | ] 2945 | 2946 | [[package]] 2947 | name = "zstd" 2948 | version = "0.13.2" 2949 | source = "registry+https://github.com/rust-lang/crates.io-index" 2950 | checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" 2951 | dependencies = [ 2952 | "zstd-safe", 2953 | ] 2954 | 2955 | [[package]] 2956 | name = "zstd-safe" 2957 | version = "7.2.1" 2958 | source = "registry+https://github.com/rust-lang/crates.io-index" 2959 | checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" 2960 | dependencies = [ 2961 | "zstd-sys", 2962 | ] 2963 | 2964 | [[package]] 2965 | name = "zstd-sys" 2966 | version = "2.0.13+zstd.1.5.6" 2967 | source = "registry+https://github.com/rust-lang/crates.io-index" 2968 | checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" 2969 | dependencies = [ 2970 | "cc", 2971 | "pkg-config", 2972 | ] 2973 | --------------------------------------------------------------------------------