diff --git a/.env.local b/.env.local index c65ff9a..4efec42 100644 --- a/.env.local +++ b/.env.local @@ -1,7 +1,7 @@ -NEXT_PUBLIC_API_KEY="AIzaSyBs3rH_4KQnO1FO7xqigU33uv654_aFI5E", -NEXT_PUBLIC_AUTH_DOMAIN="realtor-clone-ad19c.firebaseapp.com", -NEXT_PUBLIC_PROJECT_ID="realtor-clone-ad19c", -NEXT_PUBLIC_STORAGE_BUCKET="realtor-clone-ad19c.appspot.com", -NEXT_PUBLIC_MESSAGING_SENDER_ID="347563487697", -NEXT_PUBLIC_APP_ID="1:347563487697:web:ecc1c0eff0221f743f30f0", +NEXT_PUBLIC_API_KEY="AIzaSyBs3rH_4KQnO1FO7xqigU33uv654_aFI5E", +NEXT_PUBLIC_AUTH_DOMAIN="realtor-clone-ad19c.firebaseapp.com", +NEXT_PUBLIC_PROJECT_ID="realtor-clone-ad19c", +NEXT_PUBLIC_STORAGE_BUCKET="realtor-clone-ad19c.appspot.com", +NEXT_PUBLIC_MESSAGING_SENDER_ID="347563487697", +NEXT_PUBLIC_APP_ID="1:347563487697:web:ecc1c0eff0221f743f30f0", NEXT_PUBLIC_RPC_URL="https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/dvHmwiGiA_uE22lKpZKLk4FoGlC_Xzy4" \ No newline at end of file diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 6cb4f9d..42567ac 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -1,87 +1,87 @@ -name: Build and Test Rust backend - -on: [push, pull_request] -permissions: read-all - -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./backend - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-registry- - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: ./backend/target - key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-build- - - name: Build (release) - run: cargo build --release - - name: Check formatting - run: cargo fmt --all -- --check - - name: Run clippy - run: cargo clippy --all -- -D warnings - - test: - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - ports: - - 5432:5432 - options: >- - --health-cmd="pg_isready -U postgres" - --health-interval=10s - --health-timeout=5s - --health-retries=5 - defaults: - run: - working-directory: ./backend - env: - DATABASE_URL: postgres://postgres:postgres@localhost/testdb - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-registry- - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: ./backend/target - key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-build- - - name: Run migrations - run: | - cargo install sqlx-cli --version 0.7.3 - sqlx migrate run - - name: Run tests - run: cargo test --all --release --locked +name: Build and Test Rust backend + +on: [push, pull_request] +permissions: read-all + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./backend + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: ./backend/target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + - name: Build (release) + run: cargo build --release + - name: Check formatting + run: cargo fmt --all -- --check + - name: Run clippy + run: cargo clippy --all -- -D warnings + + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testdb + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U postgres" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + defaults: + run: + working-directory: ./backend + env: + DATABASE_URL: postgres://postgres:postgres@localhost/testdb + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: ./backend/target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + - name: Run migrations + run: | + cargo install sqlx-cli --version 0.7.3 + sqlx migrate run + - name: Run tests + run: cargo test --all --release --locked diff --git a/.github/workflows/test_contract.yml b/.github/workflows/test_contract.yml index a79b3f0..8d0a73d 100644 --- a/.github/workflows/test_contract.yml +++ b/.github/workflows/test_contract.yml @@ -1,30 +1,30 @@ -name: Build and Test - -on: [push, pull_request] -permissions: read-all - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: software-mansion/setup-scarb@v1 - with: - scarb-version: 2.11.3 - - name: Check cairo format - run: scarb fmt --check - - name: Build cairo programs - run: scarb build - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: software-mansion/setup-scarb@v1 - with: - scarb-version: 2.11.2 - - uses: foundry-rs/setup-snfoundry@v3 - with: - starknet-foundry-version: 0.39.0 - - name: Run cairo tests +name: Build and Test + +on: [push, pull_request] +permissions: read-all + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: software-mansion/setup-scarb@v1 + with: + scarb-version: 2.11.3 + - name: Check cairo format + run: scarb fmt --check + - name: Build cairo programs + run: scarb build + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: software-mansion/setup-scarb@v1 + with: + scarb-version: 2.11.2 + - uses: foundry-rs/setup-snfoundry@v3 + with: + starknet-foundry-version: 0.39.0 + - name: Run cairo tests run: snforge test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 73aa31e..44b65c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -target -.snfoundry_cache/ +target +.snfoundry_cache/ diff --git a/.tool-versions b/.tool-versions index 8cb8e5b..e4c14f4 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.11.3 -starknet-foundry 0.40.0 +scarb 2.11.3 +starknet-foundry 0.39.0 diff --git a/Makefile b/Makefile index 7011a22..7681059 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -pr: - scarb fmt - scarb build - snforge test - -help: +pr: + scarb fmt + scarb build + snforge test + +help: @echo "make pr - Run formatting, build, and tests before PR" \ No newline at end of file diff --git a/README.md b/README.md index 9bee925..30b3e7a 100644 --- a/README.md +++ b/README.md @@ -1,182 +1,363 @@ -# PrediFi - Decentralized outcome prediction protocol (on-chain prediction platform ) - -Telegram Community: [here](https://t.me/predifi_onchain_build/1) -## Project Overview: -PrediFi is a decentralized prediction protocol built on StarkNet. In a trustless, transparent, and secure environment, it allows users to predict future outcomes across various fields, including sports, finance, and global events. By utilizing starknet technology, PrediFi ensures that all predictions and their results are verifiable onchain and immutable, thus eliminating the need for intermediaries. - -PrediFi is a groundbreaking decentralized platform designed to empower individuals, influencers, and communities to enter the dynamic world of prediction markets. Leveraging the transformative power of blockchain technology, PrediFi allows anyone to establish custom prediction markets focused on any event imaginable. This innovative approach provides a lively, engaging, and rewarding way to foster interaction within your community while monetizing the buzz and excitement surrounding trending topics. - -In our fast-paced digital age, conversations about predictions are captivating; they span a wide range of subjects, from sports matchups and political elections to the latest pop culture phenomena. Imagine if you could transform those engaging discussions into tangible rewards! With PrediFi, you have the opportunity to create markets where individuals can wager on the outcomes of these events, turning their insights and forecasts into real-world returns. - -PrediFi makes it easy to create prediction pools for a wide range of cultural and local events. You can set up pools for major sports championships and awards shows, but that's just the beginning. It's also perfect for engaging with the latest viral trends, community events, environmental happenings, and anything else that sparks buzz in your area. Whether it’s predicting the outcome of a local music festival or the next viral sensation. - -## Contract Structure - -The PrediFi protocol is modular and organized for clarity, security, and extensibility. Below is an overview of the main contract files and their purposes: - -- `src/predifi.cairo`: Main contract logic, including pool management, staking, validation, and dispute resolution. -- `src/interfaces/IERC20.cairo`: ERC20 token interface for STRK and other tokens. -- `src/interfaces/ipredifi.cairo`: Main protocol interface, including pool management, dispute, and validator traits. -- `src/interfaces/iUtils.cairo`: Utility interface (e.g., for price feeds). -- `src/base/types.cairo`: Enums and structs for pools, statuses, categories, odds, stakes, and validators. -- `src/base/events.cairo`: All protocol events (e.g., BetPlaced, UserStaked, PoolResolved). -- `src/base/errors.cairo`: Centralized error messages and codes for all protocol operations. - -## Developer Documentation & NatSpec - -All public and external functions, types, and events are documented using Cairo NatSpec comments for auditability and developer clarity. - -**Example:** -```cairo -/// @notice Places a bet on a pool. -/// @dev Transfers tokens from user, updates odds, and emits BetPlaced event. -/// @param pool_id The pool ID. -/// @param option The option to bet on. -/// @param amount The amount to bet. -fn vote(ref self: ContractState, pool_id: u256, option: felt252, amount: u256) { ... } -``` - -**Guidelines:** -- Use `@notice` for a summary of the function/type/event. -- Use `@dev` for developer/auditor notes. -- Use `@param` and `@return` for all parameters and return values. -- All new public/external functions must include NatSpec comments. - - -## Development: - -Requirements: -- Rust -- Cairo -- Starknet foundry -- Node -- Pnpm - -## Installation Guide: - -Step 1: - -1. Fork the repo - -2. Clone the forked repo to your local machine - ``` bash - git clone https://github.com/your-user-name/auto-swap - ``` - -3. Setup contract: - - ``` - cd contracts - ``` - - // Install asdf scarb and starknet foundry: - - ``` bash - curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh - ``` - - // Method 2: - - Install asdf and install scarb, and starknet foundry: https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html - -4. Add development tools - ``` bash - asdf set --home scarb 2.9.2 - - asdf set --home starknet-foundry 0.36.0 - - ``` - -5. Ensure installed properly - - ``` bash - snforge --version - - scarb --version - ``` - -6. Build -``` bash -scarb build -``` -7. Test -``` bash -snforge test -``` - -## 🐳 DevContainer Setup (Docker) - -We provide a **Docker DevContainer** to simplify development and avoid local dependency issues. - -### Prerequisites -- [Docker](https://docs.docker.com/get-docker/) installed and running -- [Visual Studio Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) - -### Setup -1. Open the project in **VS Code**. -2. Press **CTRL + SHIFT + P** β†’ select **β€œDev Containers: Reopen in Container”**. -3. The container will build and install all required tools automatically. - -### Verify -Inside the container, run: - -```bash -scarb build -scarb test -scarb fmt -scarb fmt --check -``` -### Note : - - If During running `scarb build` throw error `killed` , Then increase your docker ram allocation and cores. and restart ! - -# Contributing - -We welcome contributions! Please follow these steps: - -## Getting Started - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/your-feature-name`) -3. Commit your changes with meaningful messages (`git commit -m 'feat: add new capability'`) -4. Test your changes thoroughly before submission - -## Testing Requirements - -Before submitting your PR: -1. Set up your environment variable: `export RPC_URL=https://api.cartridge.gg/x/starknet/mainnet` -2. All tests must pass locally before proceeding - -## Pull Request Process - -1. Ensure your branch is up to date with main (`git pull origin main`) -2. Include comprehensive test cases covering your changes -3. Update documentation to reflect your modifications -4. Provide a detailed description in your PR explaining: - - The problem solved - - Implementation approach - - Any potential impacts -5. Request review from project maintainers - -## Code Standards - -- Follow the existing code style and conventions -- Write clean, readable, and maintainable code -- Include comments for complex logic -- Keep commits focused and atomic -- **All new public/external functions must include NatSpec comments** - -## Support - -Need help with your contribution? You can: -- Open an issue in the GitHub repository -- Join our Telegram channel for community assistance -- Check existing documentation and discussions - -We aim to review all contributions promptly and appreciate your efforts to improve the project! - -## Security & Auditing - -- All critical logic is documented with NatSpec. -- Please report vulnerabilities responsibly. - + +# PrediFi - Decentralized outcome prediction protocol (on-chain prediction platform ) + +Telegram Community: [here](https://t.me/predifi_onchain_build/1) +## Project Overview: +PrediFi is a decentralized prediction protocol built on StarkNet. In a trustless, transparent, and secure environment, it allows users to predict future outcomes across various fields, including sports, finance, and global events. By utilizing starknet technology, PrediFi ensures that all predictions and their results are verifiable onchain and immutable, thus eliminating the need for intermediaries. + +PrediFi is a groundbreaking decentralized platform designed to empower individuals, influencers, and communities to enter the dynamic world of prediction markets. Leveraging the transformative power of blockchain technology, PrediFi allows anyone to establish custom prediction markets focused on any event imaginable. This innovative approach provides a lively, engaging, and rewarding way to foster interaction within your community while monetizing the buzz and excitement surrounding trending topics. + +In our fast-paced digital age, conversations about predictions are captivating; they span a wide range of subjects, from sports matchups and political elections to the latest pop culture phenomena. Imagine if you could transform those engaging discussions into tangible rewards! With PrediFi, you have the opportunity to create markets where individuals can wager on the outcomes of these events, turning their insights and forecasts into real-world returns. + +PrediFi makes it easy to create prediction pools for a wide range of cultural and local events. You can set up pools for major sports championships and awards shows, but that's just the beginning. It's also perfect for engaging with the latest viral trends, community events, environmental happenings, and anything else that sparks buzz in your area. Whether it’s predicting the outcome of a local music festival or the next viral sensation. + +## Contract Structure + +The PrediFi protocol is modular and organized for clarity, security, and extensibility. Below is an overview of the main contract files and their purposes: + +- `src/predifi.cairo`: Main contract logic, including pool management, staking, validation, and dispute resolution. +- `src/interfaces/IERC20.cairo`: ERC20 token interface for STRK and other tokens. +- `src/interfaces/ipredifi.cairo`: Main protocol interface, including pool management, dispute, and validator traits. +- `src/interfaces/iUtils.cairo`: Utility interface (e.g., for price feeds). +- `src/base/types.cairo`: Enums and structs for pools, statuses, categories, odds, stakes, and validators. +- `src/base/events.cairo`: All protocol events (e.g., BetPlaced, UserStaked, PoolResolved). +- `src/base/errors.cairo`: Centralized error messages and codes for all protocol operations. + +## Developer Documentation & NatSpec + +All public and external functions, types, and events are documented using Cairo NatSpec comments for auditability and developer clarity. + +**Example:** +```cairo +/// @notice Places a bet on a pool. +/// @dev Transfers tokens from user, updates odds, and emits BetPlaced event. +/// @param pool_id The pool ID. +/// @param option The option to bet on. +/// @param amount The amount to bet. +fn vote(ref self: ContractState, pool_id: u256, option: felt252, amount: u256) { ... } +``` + +**Guidelines:** +- Use `@notice` for a summary of the function/type/event. +- Use `@dev` for developer/auditor notes. +- Use `@param` and `@return` for all parameters and return values. +- All new public/external functions must include NatSpec comments. + + +## Development: + +Requirements: +- Rust +- Cairo +- Starknet foundry +- Node +- Pnpm + +## Installation Guide: + +Step 1: + +1. Fork the repo + +2. Clone the forked repo to your local machine + ``` bash + git clone https://github.com/your-user-name/auto-swap + ``` + +3. Setup contract: + + ``` + cd contracts + ``` + + // Install asdf scarb and starknet foundry: + + ``` bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh + ``` + + // Method 2: + + Install asdf and install scarb, and starknet foundry: https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html + +4. Add development tools + ``` bash + asdf set --home scarb 2.9.2 + + asdf set --home starknet-foundry 0.36.0 + + ``` + +5. Ensure installed properly + + ``` bash + snforge --version + + scarb --version + ``` + +6. Build +``` bash +scarb build +``` +7. Test +``` bash +snforge test +``` +## 🐳 DevContainer Setup (Docker) + +We provide a **Docker DevContainer** to simplify development and avoid local dependency issues. + +### Prerequisites +- [Docker](https://docs.docker.com/get-docker/) installed and running +- [Visual Studio Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +### Setup +1. Open the project in **VS Code**. +2. Press **CTRL + SHIFT + P** β†’ select **β€œDev Containers: Reopen in Container”**. +3. The container will build and install all required tools automatically. + +### Verify +Inside the container, run: + +```bash +scarb build +scarb test +scarb fmt +scarb fmt --check +``` +### Note : + - If During running `scarb build` throw error `killed` , Then increase your docker ram allocation and cores. and restart ! + +# Contributing + +We welcome contributions! Please follow these steps: + +## Getting Started + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/your-feature-name`) +3. Commit your changes with meaningful messages (`git commit -m 'feat: add new capability'`) +4. Test your changes thoroughly before submission + +## Testing Requirements + +Before submitting your PR: +1. Set up your environment variable: `export RPC_URL=https://api.cartridge.gg/x/starknet/mainnet` +2. All tests must pass locally before proceeding + +## Pull Request Process + +1. Ensure your branch is up to date with main (`git pull origin main`) +2. Include comprehensive test cases covering your changes +3. Update documentation to reflect your modifications +4. Provide a detailed description in your PR explaining: + - The problem solved + - Implementation approach + - Any potential impacts +5. Request review from project maintainers + +## Code Standards + +- Follow the existing code style and conventions +- Write clean, readable, and maintainable code +- Include comments for complex logic +- Keep commits focused and atomic +- **All new public/external functions must include NatSpec comments** + +## Support + +Need help with your contribution? You can: +- Open an issue in the GitHub repository +- Join our Telegram channel for community assistance +- Check existing documentation and discussions + +We aim to review all contributions promptly and appreciate your efforts to improve the project! + +## Security & Auditing + +- All critical logic is documented with NatSpec. +- Please report vulnerabilities responsibly. + +# PrediFi - Decentralized outcome prediction protocol (on-chain prediction platform ) + +Telegram Community: [here](https://t.me/predifi_onchain_build/1) +## Project Overview: +PrediFi is a decentralized prediction protocol built on StarkNet. In a trustless, transparent, and secure environment, it allows users to predict future outcomes across various fields, including sports, finance, and global events. By utilizing starknet technology, PrediFi ensures that all predictions and their results are verifiable onchain and immutable, thus eliminating the need for intermediaries. + +PrediFi is a groundbreaking decentralized platform designed to empower individuals, influencers, and communities to enter the dynamic world of prediction markets. Leveraging the transformative power of blockchain technology, PrediFi allows anyone to establish custom prediction markets focused on any event imaginable. This innovative approach provides a lively, engaging, and rewarding way to foster interaction within your community while monetizing the buzz and excitement surrounding trending topics. + +In our fast-paced digital age, conversations about predictions are captivating; they span a wide range of subjects, from sports matchups and political elections to the latest pop culture phenomena. Imagine if you could transform those engaging discussions into tangible rewards! With PrediFi, you have the opportunity to create markets where individuals can wager on the outcomes of these events, turning their insights and forecasts into real-world returns. + +PrediFi makes it easy to create prediction pools for a wide range of cultural and local events. You can set up pools for major sports championships and awards shows, but that's just the beginning. It's also perfect for engaging with the latest viral trends, community events, environmental happenings, and anything else that sparks buzz in your area. Whether it’s predicting the outcome of a local music festival or the next viral sensation. + +## Contract Structure + +The PrediFi protocol is modular and organized for clarity, security, and extensibility. Below is an overview of the main contract files and their purposes: + +- `src/predifi.cairo`: Main contract logic, including pool management, staking, validation, and dispute resolution. +- `src/interfaces/IERC20.cairo`: ERC20 token interface for STRK and other tokens. +- `src/interfaces/ipredifi.cairo`: Main protocol interface, including pool management, dispute, and validator traits. +- `src/interfaces/iUtils.cairo`: Utility interface (e.g., for price feeds). +- `src/base/types.cairo`: Enums and structs for pools, statuses, categories, odds, stakes, and validators. +- `src/base/events.cairo`: All protocol events (e.g., BetPlaced, UserStaked, PoolResolved). +- `src/base/errors.cairo`: Centralized error messages and codes for all protocol operations. + +## Developer Documentation & NatSpec + +All public and external functions, types, and events are documented using Cairo NatSpec comments for auditability and developer clarity. + +**Example:** +```cairo +/// @notice Places a bet on a pool. +/// @dev Transfers tokens from user, updates odds, and emits BetPlaced event. +/// @param pool_id The pool ID. +/// @param option The option to bet on. +/// @param amount The amount to bet. +fn vote(ref self: ContractState, pool_id: u256, option: felt252, amount: u256) { ... } +``` + +**Guidelines:** +- Use `@notice` for a summary of the function/type/event. +- Use `@dev` for developer/auditor notes. +- Use `@param` and `@return` for all parameters and return values. +- All new public/external functions must include NatSpec comments. + + +## Development: + +Requirements: +- Rust +- Cairo +- Starknet foundry +- Node +- Pnpm + +## Installation Guide: + +Step 1: + +1. Fork the repo + +2. Clone the forked repo to your local machine + ``` bash + git clone https://github.com/your-user-name/auto-swap + ``` + +3. Setup contract: + + ``` + cd contracts + ``` + + // Install asdf scarb and starknet foundry: + + ``` bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.dev | sh + ``` + + // Method 2: + + Install asdf and install scarb, and starknet foundry: https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html + +4. Add development tools + ``` bash + asdf set --home scarb 2.9.2 + + asdf set --home starknet-foundry 0.36.0 + + ``` + +5. Ensure installed properly + + ``` bash + snforge --version + + scarb --version + ``` + +6. Build +``` bash +scarb build +``` +7. Test +``` bash +snforge test +``` + +## 🐳 DevContainer Setup (Docker) + +We provide a **Docker DevContainer** to simplify development and avoid local dependency issues. + +### Prerequisites +- [Docker](https://docs.docker.com/get-docker/) installed and running +- [Visual Studio Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +### Setup +1. Open the project in **VS Code**. +2. Press **CTRL + SHIFT + P** β†’ select **β€œDev Containers: Reopen in Container”**. +3. The container will build and install all required tools automatically. + +### Verify +Inside the container, run: + +```bash +scarb build +scarb test +scarb fmt +scarb fmt --check +``` +### Note : + - If During running `scarb build` throw error `killed` , Then increase your docker ram allocation and cores. and restart ! + +# Contributing + +We welcome contributions! Please follow these steps: + +## Getting Started + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/your-feature-name`) +3. Commit your changes with meaningful messages (`git commit -m 'feat: add new capability'`) +4. Test your changes thoroughly before submission + +## Testing Requirements + +Before submitting your PR: +1. Set up your environment variable: `export RPC_URL=https://api.cartridge.gg/x/starknet/mainnet` +2. All tests must pass locally before proceeding + +## Pull Request Process + +1. Ensure your branch is up to date with main (`git pull origin main`) +2. Include comprehensive test cases covering your changes +3. Update documentation to reflect your modifications +4. Provide a detailed description in your PR explaining: + - The problem solved + - Implementation approach + - Any potential impacts +5. Request review from project maintainers + +## Code Standards + +- Follow the existing code style and conventions +- Write clean, readable, and maintainable code +- Include comments for complex logic +- Keep commits focused and atomic +- **All new public/external functions must include NatSpec comments** + +## Support + +Need help with your contribution? You can: +- Open an issue in the GitHub repository +- Join our Telegram channel for community assistance +- Check existing documentation and discussions + +We aim to review all contributions promptly and appreciate your efforts to improve the project! + +## Security & Auditing + +- All critical logic is documented with NatSpec. +- Please report vulnerabilities responsibly. + **For more details, see the inline NatSpec documentation in each contract file.** \ No newline at end of file diff --git a/Scarb.toml b/Scarb.toml index ffba8be..24e9823 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,57 +1,57 @@ -[package] -name = "contract" -version = "0.1.0" -edition = "2024_07" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html - -[dependencies] -starknet = ">=2.11.2" -openzeppelin = "1.0.0" -pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } - -[dev-dependencies] -snforge_std = "0.39.0" -assert_macros = "2.9.4" - -[[target.starknet-contract]] -sierra = true - -[scripts] -test = "snforge test" - -[[tool.snforge.fork]] -name = "SEPOLIA_LATEST" -url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_7" -block_id.tag = "latest" - - -# Visit https://foundry-rs.github.io/starknet-foundry/appendix/scarb-toml.html for more information - -# [tool.snforge] # Define `snforge` tool section -# exit_first = true # Stop tests execution immediately upon the first failure -# fuzzer_runs = 1234 # Number of runs of the random fuzzer -# fuzzer_seed = 1111 # Seed for the random fuzzer - -# [[tool.snforge.fork]] # Used for fork testing -# name = "SOME_NAME" # Fork name -# url = "http://your.rpc.url" # Url of the RPC provider -# block_id.tag = "latest" # Block to fork from (block tag) - -# [[tool.snforge.fork]] -# name = "SOME_SECOND_NAME" -# url = "http://your.second.rpc.url" -# block_id.number = "123" # Block to fork from (block number) - -# [[tool.snforge.fork]] -# name = "SOME_THIRD_NAME" -# url = "http://your.third.rpc.url" -# block_id.hash = "0x123" # Block to fork from (block hash) - -# [profile.dev.cairo] # Configure Cairo compiler -# unstable-add-statements-code-locations-debug-info = true # Should be used if you want to use coverage -# unstable-add-statements-functions-debug-info = true # Should be used if you want to use coverage/profiler -# inlining-strategy = "avoid" # Should be used if you want to use coverage - -# [features] # Used for conditional compilation -# enable_for_tests = [] # Feature name and list of other features that should be enabled with it +[package] +name = "contract" +version = "0.1.0" +edition = "2024_07" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = ">=2.11.2" +openzeppelin = "1.0.0" +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } + +[dev-dependencies] +snforge_std = "0.39.0" +assert_macros = "2.9.4" + +[[target.starknet-contract]] +sierra = true + +[scripts] +test = "snforge test" + +[[tool.snforge.fork]] +name = "SEPOLIA_LATEST" +url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_7" +block_id.tag = "latest" + + +# Visit https://foundry-rs.github.io/starknet-foundry/appendix/scarb-toml.html for more information + +# [tool.snforge] # Define `snforge` tool section +# exit_first = true # Stop tests execution immediately upon the first failure +# fuzzer_runs = 1234 # Number of runs of the random fuzzer +# fuzzer_seed = 1111 # Seed for the random fuzzer + +# [[tool.snforge.fork]] # Used for fork testing +# name = "SOME_NAME" # Fork name +# url = "http://your.rpc.url" # Url of the RPC provider +# block_id.tag = "latest" # Block to fork from (block tag) + +# [[tool.snforge.fork]] +# name = "SOME_SECOND_NAME" +# url = "http://your.second.rpc.url" +# block_id.number = "123" # Block to fork from (block number) + +# [[tool.snforge.fork]] +# name = "SOME_THIRD_NAME" +# url = "http://your.third.rpc.url" +# block_id.hash = "0x123" # Block to fork from (block hash) + +# [profile.dev.cairo] # Configure Cairo compiler +# unstable-add-statements-code-locations-debug-info = true # Should be used if you want to use coverage +# unstable-add-statements-functions-debug-info = true # Should be used if you want to use coverage/profiler +# inlining-strategy = "avoid" # Should be used if you want to use coverage + +# [features] # Used for conditional compilation +# enable_for_tests = [] # Feature name and list of other features that should be enabled with it diff --git a/backend/.env.backup b/backend/.env.backup index 4311a41..798e917 100644 --- a/backend/.env.backup +++ b/backend/.env.backup @@ -1,2 +1,2 @@ -DATABASE_URL=postgres://user:password@localhost:5432/predifi-db +DATABASE_URL=postgres://user:password@localhost:5432/predifi-db DATABASE_MAX_CONNECTIONS=5 \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index 557cd93..f4940ac 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,2 +1,2 @@ -DATABASE_URL=postgres://user:password@localhost:5432/predifi-db -DATABASE_MAX_CONNECTIONS=5 +DATABASE_URL=postgres://user:password@localhost:5432/predifi-db +DATABASE_MAX_CONNECTIONS=5 diff --git a/backend/.github/workflows/ci.yml b/backend/.github/workflows/ci.yml index 5d031bd..b70b1e1 100644 --- a/backend/.github/workflows/ci.yml +++ b/backend/.github/workflows/ci.yml @@ -1,141 +1,141 @@ -name: Predifi Backend CI/CD - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb - -jobs: - test: - name: Integration Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - - name: Cache Rust dependencies - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Install SQLx CLI - run: cargo install sqlx-cli --no-default-features --features postgres - - - name: Wait for PostgreSQL - run: | - echo "⏳ Waiting for PostgreSQL to be ready..." - timeout 60s bash -c 'until pg_isready -h localhost -p 5432 -U postgres; do sleep 1; done' - echo "βœ… PostgreSQL is ready!" - - - name: Setup test database - run: | - echo "πŸ—„οΈ Setting up test database..." - sqlx database create - sqlx migrate run - echo "βœ… Test database ready!" - - - name: Run unit tests - run: | - echo "πŸ§ͺ Running unit tests..." - cargo test --lib --bins - echo "βœ… Unit tests completed!" - - - name: Run integration tests - run: | - echo "πŸ”— Running integration tests..." - cargo test --tests -- --nocapture - echo "βœ… Integration tests completed!" - - - name: Run code quality checks - run: | - echo "πŸ” Running code quality checks..." - cargo clippy -- -D warnings - cargo fmt -- --check - echo "βœ… Code quality checks passed!" - - - name: Build release version - run: | - echo "πŸ—οΈ Building release version..." - cargo build --release - echo "βœ… Release build successful!" - - security: - name: Security Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - - name: Run security audit - run: | - echo "πŸ”’ Running security audit..." - cargo audit --version || cargo install cargo-audit --features vendored-openssl - cargo audit - echo "βœ… Security audit completed!" - - notify: - name: Test Results Summary - needs: [test, security] - runs-on: ubuntu-latest - if: always() - - steps: - - name: Success notification - if: success() - run: | - echo "πŸŽ‰ All tests passed successfully!" - echo "βœ… Integration tests: ${{ needs.test.result }}" - echo "βœ… Security audit: ${{ needs.security.result }}" - echo "πŸš€ Ready for deployment!" - - - name: Failure notification - if: failure() - run: | - echo "❌ CI/CD Pipeline failed!" - echo "πŸ” Check the logs above for details." - echo "πŸ“Š Test results: ${{ needs.test.result }}" - echo "πŸ”’ Security results: ${{ needs.security.result }}" +name: Predifi Backend CI/CD + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb + +jobs: + test: + name: Integration Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: testdb + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Cache Rust dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install SQLx CLI + run: cargo install sqlx-cli --no-default-features --features postgres + + - name: Wait for PostgreSQL + run: | + echo "⏳ Waiting for PostgreSQL to be ready..." + timeout 60s bash -c 'until pg_isready -h localhost -p 5432 -U postgres; do sleep 1; done' + echo "βœ… PostgreSQL is ready!" + + - name: Setup test database + run: | + echo "πŸ—„οΈ Setting up test database..." + sqlx database create + sqlx migrate run + echo "βœ… Test database ready!" + + - name: Run unit tests + run: | + echo "πŸ§ͺ Running unit tests..." + cargo test --lib --bins + echo "βœ… Unit tests completed!" + + - name: Run integration tests + run: | + echo "πŸ”— Running integration tests..." + cargo test --tests -- --nocapture + echo "βœ… Integration tests completed!" + + - name: Run code quality checks + run: | + echo "πŸ” Running code quality checks..." + cargo clippy -- -D warnings + cargo fmt -- --check + echo "βœ… Code quality checks passed!" + + - name: Build release version + run: | + echo "πŸ—οΈ Building release version..." + cargo build --release + echo "βœ… Release build successful!" + + security: + name: Security Audit + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Run security audit + run: | + echo "πŸ”’ Running security audit..." + cargo audit --version || cargo install cargo-audit --features vendored-openssl + cargo audit + echo "βœ… Security audit completed!" + + notify: + name: Test Results Summary + needs: [test, security] + runs-on: ubuntu-latest + if: always() + + steps: + - name: Success notification + if: success() + run: | + echo "πŸŽ‰ All tests passed successfully!" + echo "βœ… Integration tests: ${{ needs.test.result }}" + echo "βœ… Security audit: ${{ needs.security.result }}" + echo "πŸš€ Ready for deployment!" + + - name: Failure notification + if: failure() + run: | + echo "❌ CI/CD Pipeline failed!" + echo "πŸ” Check the logs above for details." + echo "πŸ“Š Test results: ${{ needs.test.result }}" + echo "πŸ”’ Security results: ${{ needs.security.result }}" echo "πŸ’‘ Fix the issues and push again." \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index 14ee500..3a88a81 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,2 @@ -target/ -.env +target/ +.env diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 788e052..62b887e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1,3656 +1,3656 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.3", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "auto-future" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "tokio", - "tower 0.4.13", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tower 0.5.2", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-test" -version = "12.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2357b21508cb580bae52125c40eb4836614bc9147777cdbffff6ef8d38795d" -dependencies = [ - "anyhow", - "auto-future", - "axum 0.6.20", - "bytes", - "cookie", - "http 0.2.12", - "hyper 0.14.32", - "reserve-port", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "tokio", - "url", -] - -[[package]] -name = "backend" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum 0.7.9", - "axum-test", - "bigdecimal", - "chrono", - "dotenv", - "dotenvy", - "futures", - "hyper 1.6.0", - "log", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry-stdout", - "opentelemetry_sdk", - "rust_decimal", - "serde", - "serde_json", - "sqlx", - "testcontainers", - "testcontainers-modules", - "thiserror 2.0.12", - "tokio", - "tokio-test", - "tower 0.5.2", - "tower-http", - "tracing", - "tracing-opentelemetry", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bigdecimal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bollard-stubs" -version = "1.42.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" -dependencies = [ - "serde", - "serde_with", -] - -[[package]] -name = "borsh" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.10.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash 0.8.12", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" - -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.3.1", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http 1.3.1", - "http-body 1.0.1", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", -] - -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.32", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - -[[package]] -name = "hyper-util" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" -dependencies = [ - "bytes", - "futures-core", - "http 1.3.1", - "http-body 1.0.1", - "hyper 1.6.0", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" -dependencies = [ - "equivalent", - "hashbrown 0.15.4", -] - -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libsqlite3-sys" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "opentelemetry" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror 1.0.69", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" -dependencies = [ - "async-trait", - "futures-core", - "http 0.2.12", - "opentelemetry", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "thiserror 1.0.69", - "tokio", - "tonic", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", - "tonic", -] - -[[package]] -name = "opentelemetry-stdout" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d080bf06af02b738feb2e6830cf72c30b76ca18b40f555cdf1b53e7b491bfe" -dependencies = [ - "async-trait", - "chrono", - "futures-util", - "opentelemetry", - "opentelemetry_sdk", - "ordered-float", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" -dependencies = [ - "async-trait", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "lazy_static", - "once_cell", - "opentelemetry", - "ordered-float", - "percent-encoding", - "rand 0.8.5", - "serde_json", - "thiserror 1.0.69", - "tokio", - "tokio-stream", -] - -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "postgres-protocol" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator", - "hmac", - "md-5", - "memchr", - "rand 0.9.2", - "sha2", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "reserve-port" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b212efd3460286cd590149feedd0afabef08ee352445dd6b4452f0d136098a5f" -dependencies = [ - "lazy_static", - "thiserror 1.0.69", -] - -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rust_decimal" -version = "1.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "postgres-types", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" -dependencies = [ - "itoa", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" -dependencies = [ - "ahash 0.8.12", - "atoi", - "bigdecimal", - "byteorder", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashlink", - "hex", - "indexmap 2.10.0", - "log", - "memchr", - "once_cell", - "paste", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlformat", - "thiserror 1.0.69", - "tokio", - "tokio-stream", - "tracing", - "url", - "uuid", -] - -[[package]] -name = "sqlx-macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 1.0.109", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 1.0.109", - "tempfile", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" -dependencies = [ - "atoi", - "base64 0.21.7", - "bigdecimal", - "bitflags 2.9.1", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 1.0.69", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" -dependencies = [ - "atoi", - "base64 0.21.7", - "bigdecimal", - "bitflags 2.9.1", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "num-bigint", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 1.0.69", - "tracing", - "uuid", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "sqlx-core", - "tracing", - "url", - "urlencoding", - "uuid", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "testcontainers" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" -dependencies = [ - "bollard-stubs", - "futures", - "hex", - "hmac", - "log", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "testcontainers-modules" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8debb5e215d9e89ea93255fffff00bf037ea44075d7a2669a21a8a988d6b52fd" -dependencies = [ - "testcontainers", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.46.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-test" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" -dependencies = [ - "async-stream", - "bytes", - "futures-core", - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-util" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.10.0", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tonic" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.6.20", - "base64 0.21.7", - "bytes", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" -dependencies = [ - "bitflags 2.9.1", - "bytes", - "http 1.3.1", - "http-body 1.0.1", - "http-body-util", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", - "uuid", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-opentelemetry" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry", - "opentelemetry_sdk", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "whoami" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" -dependencies = [ - "redox_syscall", - "wasite", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "auto-future" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "tokio", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-test" +version = "12.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2357b21508cb580bae52125c40eb4836614bc9147777cdbffff6ef8d38795d" +dependencies = [ + "anyhow", + "auto-future", + "axum 0.6.20", + "bytes", + "cookie", + "http 0.2.12", + "hyper 0.14.32", + "reserve-port", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "url", +] + +[[package]] +name = "backend" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.9", + "axum-test", + "bigdecimal", + "chrono", + "dotenv", + "dotenvy", + "futures", + "hyper 1.6.0", + "log", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-stdout", + "opentelemetry_sdk", + "rust_decimal", + "serde", + "serde_json", + "sqlx", + "testcontainers", + "testcontainers-modules", + "thiserror 2.0.12", + "tokio", + "tokio-test", + "tower 0.5.2", + "tower-http", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bollard-stubs" +version = "1.42.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" +dependencies = [ + "serde", + "serde_with", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.10.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.32", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror 1.0.69", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" +dependencies = [ + "async-trait", + "futures-core", + "http 0.2.12", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror 1.0.69", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-stdout" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d080bf06af02b738feb2e6830cf72c30b76ca18b40f555cdf1b53e7b491bfe" +dependencies = [ + "async-trait", + "chrono", + "futures-util", + "opentelemetry", + "opentelemetry_sdk", + "ordered-float", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "lazy_static", + "once_cell", + "opentelemetry", + "ordered-float", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "postgres-protocol" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand 0.9.2", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reserve-port" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b212efd3460286cd590149feedd0afabef08ee352445dd6b4452f0d136098a5f" +dependencies = [ + "lazy_static", + "thiserror 1.0.69", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust_decimal" +version = "1.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "postgres-types", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.12", + "atoi", + "bigdecimal", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.10.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bigdecimal", + "bitflags 2.9.1", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bigdecimal", + "bitflags 2.9.1", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "testcontainers" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" +dependencies = [ + "bollard-stubs", + "futures", + "hex", + "hmac", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "testcontainers-modules" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8debb5e215d9e89ea93255fffff00bf037ea44075d7a2669a21a8a988d6b52fd" +dependencies = [ + "testcontainers", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.10.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4b8ed41..8f7d6b6 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,40 +1,40 @@ -[package] -name = "backend" -version = "0.1.0" -edition = "2024" - -[dependencies] -axum = "0.7" -tokio = { version = "1.37", features = ["full"] } -sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "macros", "uuid", "chrono", "bigdecimal"] } -dotenv = "0.15" -dotenvy = "0.15.7" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } -chrono = { version = "0.4", features = ["serde"] } -# OpenTelemetry dependencies -opentelemetry = { version = "0.23", features = ["trace"] } -opentelemetry-stdout = "0.4" -opentelemetry-otlp = "0.16" -opentelemetry_sdk = { version = "0.23", features = ["trace", "rt-tokio"] } -tracing-opentelemetry = "0.24" -uuid = { version = "1.0", features = ["v4"] } -# HTTP tracing middleware -tower-http = { version = "0.5", features = ["trace", "request-id"] } -tower = "0.5.2" -thiserror = "2.0.12" -anyhow = "1.0.98" -log = "0.4" -rust_decimal = { version = "1.37", features = ["serde", "db-postgres"] } -bigdecimal = { version = "0.3", features = ["serde"] } -futures = "0.3" -hyper = "1.6.0" - -[dev-dependencies] -testcontainers = "0.15" -testcontainers-modules = { version = "0.3", features = ["postgres"] } -axum-test = "12.0" -tower = { version = "0.5.2", features = ["util"] } -tokio-test = "0.4" +[package] +name = "backend" +version = "0.1.0" +edition = "2024" + +[dependencies] +axum = "0.7" +tokio = { version = "1.37", features = ["full"] } +sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "macros", "uuid", "chrono", "bigdecimal"] } +dotenv = "0.15" +dotenvy = "0.15.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +chrono = { version = "0.4", features = ["serde"] } +# OpenTelemetry dependencies +opentelemetry = { version = "0.23", features = ["trace"] } +opentelemetry-stdout = "0.4" +opentelemetry-otlp = "0.16" +opentelemetry_sdk = { version = "0.23", features = ["trace", "rt-tokio"] } +tracing-opentelemetry = "0.24" +uuid = { version = "1.0", features = ["v4"] } +# HTTP tracing middleware +tower-http = { version = "0.5", features = ["trace", "request-id"] } +tower = "0.5.2" +thiserror = "2.0.12" +anyhow = "1.0.98" +log = "0.4" +rust_decimal = { version = "1.37", features = ["serde", "db-postgres"] } +bigdecimal = { version = "0.3", features = ["serde"] } +futures = "0.3" +hyper = "1.6.0" + +[dev-dependencies] +testcontainers = "0.15" +testcontainers-modules = { version = "0.3", features = ["postgres"] } +axum-test = "12.0" +tower = { version = "0.5.2", features = ["util"] } +tokio-test = "0.4" diff --git a/backend/README.md b/backend/README.md index 4902c3b..5a2d792 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,140 +1,140 @@ -# Predifi Backend - -[![CI/CD Pipeline](https://github.com/your-username/predifi-backend/workflows/Predifi%20Backend%20CI%2FCD/badge.svg)](https://github.com/your-username/predifi-backend/actions) - -A robust, production-ready backend service for the Predifi platform with comprehensive integration testing and CI/CD automation. - -## Backend Setup - -## 1. Environment Configuration - -1. Copy the example environment file: - ```sh - cp .env.example .env - ``` -2. Edit `.env` if needed to match your local setup (the default should work with Docker Compose). - -## 2. Start the Database - -Start the PostgreSQL database using Docker Compose: -```sh -docker compose up -d -``` -This will run the database in the background. - -## 3. Run the Backend - -Start the backend server locally: -```sh -cargo run -``` -You can also use cargo watch which works like nodemon: -```sh -cargo watch -x run -``` - -The backend will connect to the database running in Docker. - -## 4. Testing - -### Local Testing Setup - -1. **Database Setup**: Ensure you have a test database running: - ```sh - # Create test database (if using local PostgreSQL) - createdb testdb - - # Or use Docker for testing - docker run --name predifi-test-db \ - -e POSTGRES_DB=testdb \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -p 5432:5432 \ - -d postgres:15 - ``` - -2. **Environment Variables**: Set up test environment: - ```sh - export DATABASE_URL="postgres://postgres:postgres@localhost:5432/testdb" - ``` - -3. **Run Tests**: - ```sh - # Run all tests - cargo test --tests -- --nocapture - - # Run specific test categories - cargo test --test test_market_api - cargo test --test test_pool_api - cargo test --test test_validator_api - - # Run with verbose output - cargo test --tests -- --nocapture --test-threads=1 - ``` - -### Test Categories - -- **Unit Tests**: `cargo test --lib --bins` -- **Integration Tests**: `cargo test --tests` -- **Health Endpoints**: Tests database connection and migrations -- **Market API**: Tests market creation, retrieval, and persistence -- **Pool API**: Tests pool management and data persistence -- **Validator API**: Tests validator operations and constraints -- **Pool Controller**: Tests direct controller function calls - -### Test Architecture - -- **Transaction Isolation**: Each test runs in its own database transaction -- **Automatic Cleanup**: Tests auto-rollback, no manual cleanup needed -- **Isolated Environment**: Tests don't interfere with each other -- **Real Database**: Tests use actual PostgreSQL with migrations - -## 5. CI/CD Pipeline - -### GitHub Actions - -The project includes a comprehensive CI/CD pipeline that runs automatically on: - -- **Push to main/develop branches** -- **Pull requests to main/develop branches** - -### Pipeline Features - -1. **Integration Testing**: Runs all tests against PostgreSQL 15 -2. **Code Quality**: Clippy linting and format checking -3. **Security Audit**: Cargo audit for vulnerability scanning -4. **Database Setup**: Automatic test database creation and migration -5. **Caching**: Optimized dependency caching for faster builds - -### Pipeline Jobs - -- **Integration Tests**: Runs all tests with PostgreSQL service -- **Security Audit**: Scans dependencies for vulnerabilities -- **Results Summary**: Provides clear pass/fail notifications - -### Local CI Validation - -To validate your changes locally before pushing: - -```sh -# Run the same checks as CI -cargo clippy -- -D warnings -cargo fmt -- --check -cargo test --tests -- --nocapture -cargo build --release -``` - -## 6. Stopping Services - -To stop the database: -```sh -docker compose down -``` - ---- - -**Note:** -- Make sure Docker is running before starting the database. -- The `.env` file must match the credentials in `docker-compose.yml`. -- All tests must pass before merging to main branch. -- The CI pipeline will automatically validate all changes. +# Predifi Backend + +[![CI/CD Pipeline](https://github.com/your-username/predifi-backend/workflows/Predifi%20Backend%20CI%2FCD/badge.svg)](https://github.com/your-username/predifi-backend/actions) + +A robust, production-ready backend service for the Predifi platform with comprehensive integration testing and CI/CD automation. + +## Backend Setup + +## 1. Environment Configuration + +1. Copy the example environment file: + ```sh + cp .env.example .env + ``` +2. Edit `.env` if needed to match your local setup (the default should work with Docker Compose). + +## 2. Start the Database + +Start the PostgreSQL database using Docker Compose: +```sh +docker compose up -d +``` +This will run the database in the background. + +## 3. Run the Backend + +Start the backend server locally: +```sh +cargo run +``` +You can also use cargo watch which works like nodemon: +```sh +cargo watch -x run +``` + +The backend will connect to the database running in Docker. + +## 4. Testing + +### Local Testing Setup + +1. **Database Setup**: Ensure you have a test database running: + ```sh + # Create test database (if using local PostgreSQL) + createdb testdb + + # Or use Docker for testing + docker run --name predifi-test-db \ + -e POSTGRES_DB=testdb \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -p 5432:5432 \ + -d postgres:15 + ``` + +2. **Environment Variables**: Set up test environment: + ```sh + export DATABASE_URL="postgres://postgres:postgres@localhost:5432/testdb" + ``` + +3. **Run Tests**: + ```sh + # Run all tests + cargo test --tests -- --nocapture + + # Run specific test categories + cargo test --test test_market_api + cargo test --test test_pool_api + cargo test --test test_validator_api + + # Run with verbose output + cargo test --tests -- --nocapture --test-threads=1 + ``` + +### Test Categories + +- **Unit Tests**: `cargo test --lib --bins` +- **Integration Tests**: `cargo test --tests` +- **Health Endpoints**: Tests database connection and migrations +- **Market API**: Tests market creation, retrieval, and persistence +- **Pool API**: Tests pool management and data persistence +- **Validator API**: Tests validator operations and constraints +- **Pool Controller**: Tests direct controller function calls + +### Test Architecture + +- **Transaction Isolation**: Each test runs in its own database transaction +- **Automatic Cleanup**: Tests auto-rollback, no manual cleanup needed +- **Isolated Environment**: Tests don't interfere with each other +- **Real Database**: Tests use actual PostgreSQL with migrations + +## 5. CI/CD Pipeline + +### GitHub Actions + +The project includes a comprehensive CI/CD pipeline that runs automatically on: + +- **Push to main/develop branches** +- **Pull requests to main/develop branches** + +### Pipeline Features + +1. **Integration Testing**: Runs all tests against PostgreSQL 15 +2. **Code Quality**: Clippy linting and format checking +3. **Security Audit**: Cargo audit for vulnerability scanning +4. **Database Setup**: Automatic test database creation and migration +5. **Caching**: Optimized dependency caching for faster builds + +### Pipeline Jobs + +- **Integration Tests**: Runs all tests with PostgreSQL service +- **Security Audit**: Scans dependencies for vulnerabilities +- **Results Summary**: Provides clear pass/fail notifications + +### Local CI Validation + +To validate your changes locally before pushing: + +```sh +# Run the same checks as CI +cargo clippy -- -D warnings +cargo fmt -- --check +cargo test --tests -- --nocapture +cargo build --release +``` + +## 6. Stopping Services + +To stop the database: +```sh +docker compose down +``` + +--- + +**Note:** +- Make sure Docker is running before starting the database. +- The `.env` file must match the credentials in `docker-compose.yml`. +- All tests must pass before merging to main branch. +- The CI pipeline will automatically validate all changes. diff --git a/backend/README_TESTING.md b/backend/README_TESTING.md index cc4b9ec..be706c8 100644 --- a/backend/README_TESTING.md +++ b/backend/README_TESTING.md @@ -1,233 +1,233 @@ -# Predifi Backend Integration Testing Guide - -This guide explains how to set up and run comprehensive integration tests for the Predifi backend. - -## πŸš€ Quick Start - -### Prerequisites - -1. **Docker Desktop** - Must be running -2. **Rust toolchain** - Latest stable version -3. **Environment setup** - Database configuration - -### Running Tests - -#### Option 1: Using the Test Script (Recommended) - -```bash -# Make the script executable (first time only) -chmod +x scripts/run_tests.sh - -# Run all integration tests -./scripts/run_tests.sh -``` - -#### Option 2: Manual Setup - -```bash -# 1. Start the test database -docker compose -f docker-compose.test.yml up -d test-db - -# 2. Wait for database to be ready -docker compose -f docker-compose.test.yml exec test-db pg_isready -U test_user -d test_db - -# 3. Set environment variables -export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" -export RUST_LOG=info - -# 4. Run the tests -cargo test --tests -- --nocapture -``` - -#### Option 3: Using Existing Database - -If you have a local PostgreSQL instance running: - -```bash -# Set the test database URL to your existing instance -export TEST_DATABASE_URL="postgres://username:password@localhost:5432/database_name" - -# Run tests -cargo test --tests -- --nocapture -``` - -## πŸ§ͺ Test Structure - -### Test Files - -- **`tests/test_health_endpoints.rs`** - Basic integration tests and database setup -- **`tests/test_market_creation.rs`** - Market endpoint tests -- **`tests/test_pool.rs`** - Pool endpoint tests -- **`tests/test_validator_api.rs`** - Validator endpoint tests - -### Test Utilities - -The integration tests include: - -- **Database setup/teardown** - Automatic migration and cleanup -- **Test fixtures** - Sample data for markets, pools, and validators -- **HTTP testing utilities** - Request/response testing helpers -- **Isolated test environment** - Each test runs in isolation - -## 🐘 Test Database Configuration - -### Docker Compose Test Setup - -The `docker-compose.test.yml` file provides: - -- **Isolated PostgreSQL 16** instance -- **Port 5433** (avoids conflicts with main database) -- **Automatic health checks** and readiness detection -- **Clean volume management** for test data - -### Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `TEST_DATABASE_URL` | `postgres://test_user:test_password@localhost:5433/test_db` | Test database connection string | -| `RUST_LOG` | `info` | Logging level for tests | - -## πŸ“‹ Test Categories - -### 1. Health & Connectivity Tests -- Database connection validation -- Migration verification -- Basic endpoint availability - -### 2. Market Endpoint Tests -- Market creation and retrieval -- Data validation and persistence -- Error handling scenarios - -### 3. Pool Endpoint Tests -- Pool creation and management -- Market association validation -- Status updates and queries - -### 4. Validator Endpoint Tests -- Validator registration -- Contract address validation -- Active status management - -## πŸ”§ Troubleshooting - -### Common Issues - -#### Docker Not Running -```bash -# Start Docker Desktop -# On macOS: Open Docker Desktop application -# On Linux: sudo systemctl start docker -``` - -#### Port Conflicts -```bash -# Check if port 5433 is in use -lsof -i :5433 - -# Modify docker-compose.test.yml to use different port -``` - -#### Database Connection Issues -```bash -# Verify test database is running -docker compose -f docker-compose.test.yml ps - -# Check database logs -docker compose -f docker-compose.test.yml logs test-db -``` - -#### Migration Failures -```bash -# Ensure migrations directory exists -ls -la migrations/ - -# Check migration files are valid SQL -cat migrations/*.sql -``` - -### Debug Mode - -Run tests with verbose output: - -```bash -# Enable debug logging -export RUST_LOG=debug - -# Run specific test with output -cargo test test_database_connection -- --nocapture - -# Run all tests with output -cargo test --tests -- --nocapture -``` - -## πŸš€ CI/CD Integration - -### GitHub Actions - -The tests are designed to run in CI/CD pipelines: - -```yaml -# Example GitHub Actions step -- name: Run Integration Tests - run: | - docker compose -f docker-compose.test.yml up -d test-db - export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" - cargo test --tests -``` - -### Local CI Simulation - -Test your CI setup locally: - -```bash -# Simulate CI environment -export CI=true -export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" - -# Run tests -cargo test --tests -``` - -## πŸ“Š Test Coverage - -### Current Coverage - -- βœ… **Database connectivity** - Connection and migration tests -- βœ… **Basic endpoint structure** - App creation and routing -- βœ… **Data fixtures** - Sample data generation -- βœ… **Cleanup utilities** - Test data isolation - -### Planned Coverage - -- πŸ”„ **HTTP endpoint testing** - Full request/response validation -- πŸ”„ **Authentication testing** - Security and access control -- πŸ”„ **Performance testing** - Load and stress testing -- πŸ”„ **Edge case testing** - Error conditions and boundary testing - -## 🀝 Contributing - -### Adding New Tests - -1. **Create test file** in `tests/` directory -2. **Use existing utilities** from test files -3. **Follow naming convention** - `test_*.rs` -4. **Include cleanup** - Always clean up test data - -### Test Best Practices - -- **Isolation** - Each test should be independent -- **Cleanup** - Always clean up after tests -- **Descriptive names** - Test names should explain what they test -- **Assertions** - Use meaningful assertions with clear error messages - -## πŸ“š Additional Resources - -- [Rust Testing Guide](https://doc.rust-lang.org/book/ch11-00-testing.html) -- [Axum Testing](https://docs.rs/axum/latest/axum/testing/index.html) -- [SQLx Testing](https://docs.rs/sqlx/latest/sqlx/testing/index.html) -- [Testcontainers Rust](https://docs.rs/testcontainers/latest/testcontainers/) - ---- - +# Predifi Backend Integration Testing Guide + +This guide explains how to set up and run comprehensive integration tests for the Predifi backend. + +## πŸš€ Quick Start + +### Prerequisites + +1. **Docker Desktop** - Must be running +2. **Rust toolchain** - Latest stable version +3. **Environment setup** - Database configuration + +### Running Tests + +#### Option 1: Using the Test Script (Recommended) + +```bash +# Make the script executable (first time only) +chmod +x scripts/run_tests.sh + +# Run all integration tests +./scripts/run_tests.sh +``` + +#### Option 2: Manual Setup + +```bash +# 1. Start the test database +docker compose -f docker-compose.test.yml up -d test-db + +# 2. Wait for database to be ready +docker compose -f docker-compose.test.yml exec test-db pg_isready -U test_user -d test_db + +# 3. Set environment variables +export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" +export RUST_LOG=info + +# 4. Run the tests +cargo test --tests -- --nocapture +``` + +#### Option 3: Using Existing Database + +If you have a local PostgreSQL instance running: + +```bash +# Set the test database URL to your existing instance +export TEST_DATABASE_URL="postgres://username:password@localhost:5432/database_name" + +# Run tests +cargo test --tests -- --nocapture +``` + +## πŸ§ͺ Test Structure + +### Test Files + +- **`tests/test_health_endpoints.rs`** - Basic integration tests and database setup +- **`tests/test_market_creation.rs`** - Market endpoint tests +- **`tests/test_pool.rs`** - Pool endpoint tests +- **`tests/test_validator_api.rs`** - Validator endpoint tests + +### Test Utilities + +The integration tests include: + +- **Database setup/teardown** - Automatic migration and cleanup +- **Test fixtures** - Sample data for markets, pools, and validators +- **HTTP testing utilities** - Request/response testing helpers +- **Isolated test environment** - Each test runs in isolation + +## 🐘 Test Database Configuration + +### Docker Compose Test Setup + +The `docker-compose.test.yml` file provides: + +- **Isolated PostgreSQL 16** instance +- **Port 5433** (avoids conflicts with main database) +- **Automatic health checks** and readiness detection +- **Clean volume management** for test data + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `TEST_DATABASE_URL` | `postgres://test_user:test_password@localhost:5433/test_db` | Test database connection string | +| `RUST_LOG` | `info` | Logging level for tests | + +## πŸ“‹ Test Categories + +### 1. Health & Connectivity Tests +- Database connection validation +- Migration verification +- Basic endpoint availability + +### 2. Market Endpoint Tests +- Market creation and retrieval +- Data validation and persistence +- Error handling scenarios + +### 3. Pool Endpoint Tests +- Pool creation and management +- Market association validation +- Status updates and queries + +### 4. Validator Endpoint Tests +- Validator registration +- Contract address validation +- Active status management + +## πŸ”§ Troubleshooting + +### Common Issues + +#### Docker Not Running +```bash +# Start Docker Desktop +# On macOS: Open Docker Desktop application +# On Linux: sudo systemctl start docker +``` + +#### Port Conflicts +```bash +# Check if port 5433 is in use +lsof -i :5433 + +# Modify docker-compose.test.yml to use different port +``` + +#### Database Connection Issues +```bash +# Verify test database is running +docker compose -f docker-compose.test.yml ps + +# Check database logs +docker compose -f docker-compose.test.yml logs test-db +``` + +#### Migration Failures +```bash +# Ensure migrations directory exists +ls -la migrations/ + +# Check migration files are valid SQL +cat migrations/*.sql +``` + +### Debug Mode + +Run tests with verbose output: + +```bash +# Enable debug logging +export RUST_LOG=debug + +# Run specific test with output +cargo test test_database_connection -- --nocapture + +# Run all tests with output +cargo test --tests -- --nocapture +``` + +## πŸš€ CI/CD Integration + +### GitHub Actions + +The tests are designed to run in CI/CD pipelines: + +```yaml +# Example GitHub Actions step +- name: Run Integration Tests + run: | + docker compose -f docker-compose.test.yml up -d test-db + export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" + cargo test --tests +``` + +### Local CI Simulation + +Test your CI setup locally: + +```bash +# Simulate CI environment +export CI=true +export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" + +# Run tests +cargo test --tests +``` + +## πŸ“Š Test Coverage + +### Current Coverage + +- βœ… **Database connectivity** - Connection and migration tests +- βœ… **Basic endpoint structure** - App creation and routing +- βœ… **Data fixtures** - Sample data generation +- βœ… **Cleanup utilities** - Test data isolation + +### Planned Coverage + +- πŸ”„ **HTTP endpoint testing** - Full request/response validation +- πŸ”„ **Authentication testing** - Security and access control +- πŸ”„ **Performance testing** - Load and stress testing +- πŸ”„ **Edge case testing** - Error conditions and boundary testing + +## 🀝 Contributing + +### Adding New Tests + +1. **Create test file** in `tests/` directory +2. **Use existing utilities** from test files +3. **Follow naming convention** - `test_*.rs` +4. **Include cleanup** - Always clean up test data + +### Test Best Practices + +- **Isolation** - Each test should be independent +- **Cleanup** - Always clean up after tests +- **Descriptive names** - Test names should explain what they test +- **Assertions** - Use meaningful assertions with clear error messages + +## πŸ“š Additional Resources + +- [Rust Testing Guide](https://doc.rust-lang.org/book/ch11-00-testing.html) +- [Axum Testing](https://docs.rs/axum/latest/axum/testing/index.html) +- [SQLx Testing](https://docs.rs/sqlx/latest/sqlx/testing/index.html) +- [Testcontainers Rust](https://docs.rs/testcontainers/latest/testcontainers/) + +--- + **Happy Testing! πŸ§ͺ✨** \ No newline at end of file diff --git a/backend/docker-compose.test.yml b/backend/docker-compose.test.yml index 9623578..26948a2 100644 --- a/backend/docker-compose.test.yml +++ b/backend/docker-compose.test.yml @@ -1,30 +1,30 @@ -version: '3.8' - -services: - test-db: - image: postgres:16 - container_name: predifi-test-db - restart: "no" - environment: - POSTGRES_USER: test_user - POSTGRES_PASSWORD: test_password - POSTGRES_DB: test_db - ports: - - "5433:5432" # Use different port to avoid conflicts - healthcheck: - test: ["CMD-SHELL", "pg_isready -U test_user -d test_db"] - interval: 1s - timeout: 5s - retries: 30 - start_period: 10s - volumes: - - test_pgdata:/var/lib/postgresql/data - networks: - - test-network - -volumes: - test_pgdata: - -networks: - test-network: +version: '3.8' + +services: + test-db: + image: postgres:16 + container_name: predifi-test-db + restart: "no" + environment: + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_password + POSTGRES_DB: test_db + ports: + - "5433:5432" # Use different port to avoid conflicts + healthcheck: + test: ["CMD-SHELL", "pg_isready -U test_user -d test_db"] + interval: 1s + timeout: 5s + retries: 30 + start_period: 10s + volumes: + - test_pgdata:/var/lib/postgresql/data + networks: + - test-network + +volumes: + test_pgdata: + +networks: + test-network: driver: bridge \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 5ffc757..3f45de5 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,20 +1,20 @@ -services: - db: - image: postgres:16 - restart: always - environment: - POSTGRES_USER: ${POSTGRES_USER:-user} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_DB:-predifi-db} - ports: - - "5432:5432" - healthcheck: - # test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER"] - interval: 5s - timeout: 5s - retries: 5 - volumes: - - pgdata:/var/lib/postgresql/data - -volumes: - pgdata: +services: + db: + image: postgres:16 + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER:-user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-predifi-db} + ports: + - "5432:5432" + healthcheck: + # test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER"] + interval: 5s + timeout: 5s + retries: 5 + volumes: + - pgdata:/var/lib/postgresql/data + +volumes: + pgdata: diff --git a/backend/migrations/20250724150300_init.sql b/backend/migrations/20250724150300_init.sql index d5152e5..64f4688 100644 --- a/backend/migrations/20250724150300_init.sql +++ b/backend/migrations/20250724150300_init.sql @@ -1,77 +1,77 @@ --- Migration: Initial tables for predifi - -CREATE TABLE market_category ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL -); - -CREATE TABLE market ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - description TEXT, - category_id INTEGER REFERENCES market_category(id), - image_url TEXT, - event_source_url TEXT, - start_time BIGINT, - lock_time BIGINT, - end_time BIGINT, - option1 TEXT, - option2 TEXT, - min_bet_amount NUMERIC, - max_bet_amount NUMERIC, - creator_fee SMALLINT, - is_private BOOLEAN, - creator_address TEXT, - created_timestamp BIGINT, - status TEXT DEFAULT 'active' -); - -CREATE TABLE tags ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL UNIQUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE TABLE market_tags ( - id SERIAL PRIMARY KEY, - market_id INTEGER REFERENCES market(id) ON DELETE CASCADE, - tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE, - UNIQUE(market_id, tag_id) -); - -CREATE TABLE pool ( - id SERIAL PRIMARY KEY, - market_id INTEGER REFERENCES market(id), - name TEXT NOT NULL, - type SMALLINT NOT NULL, - description TEXT, - image_url TEXT, - event_source_url TEXT, - start_time BIGINT, - lock_time BIGINT, - end_time BIGINT, - option1 TEXT, - option2 TEXT, - min_bet_amount NUMERIC, - max_bet_amount NUMERIC, - creator_fee SMALLINT, - is_private BOOLEAN, - category_id INTEGER REFERENCES market_category(id) -); - --- Add pool_status enum type -DO $$ BEGIN - CREATE TYPE pool_status AS ENUM ('Active', 'Locked', 'Settled', 'Closed'); -EXCEPTION - WHEN duplicate_object THEN NULL; -END $$; - -ALTER TABLE pool ADD COLUMN status pool_status NOT NULL DEFAULT 'Active'; -CREATE INDEX idx_pool_status ON pool(status); - -CREATE TABLE user_pool ( - id SERIAL PRIMARY KEY, - user_id TEXT NOT NULL, - pool_id INTEGER REFERENCES pool(id), - amount_staked NUMERIC NOT NULL -); +-- Migration: Initial tables for predifi + +CREATE TABLE market_category ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL +); + +CREATE TABLE market ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + category_id INTEGER REFERENCES market_category(id), + image_url TEXT, + event_source_url TEXT, + start_time BIGINT, + lock_time BIGINT, + end_time BIGINT, + option1 TEXT, + option2 TEXT, + min_bet_amount NUMERIC, + max_bet_amount NUMERIC, + creator_fee SMALLINT, + is_private BOOLEAN, + creator_address TEXT, + created_timestamp BIGINT, + status TEXT DEFAULT 'active' +); + +CREATE TABLE tags ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE TABLE market_tags ( + id SERIAL PRIMARY KEY, + market_id INTEGER REFERENCES market(id) ON DELETE CASCADE, + tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(market_id, tag_id) +); + +CREATE TABLE pool ( + id SERIAL PRIMARY KEY, + market_id INTEGER REFERENCES market(id), + name TEXT NOT NULL, + type SMALLINT NOT NULL, + description TEXT, + image_url TEXT, + event_source_url TEXT, + start_time BIGINT, + lock_time BIGINT, + end_time BIGINT, + option1 TEXT, + option2 TEXT, + min_bet_amount NUMERIC, + max_bet_amount NUMERIC, + creator_fee SMALLINT, + is_private BOOLEAN, + category_id INTEGER REFERENCES market_category(id) +); + +-- Add pool_status enum type +DO $$ BEGIN + CREATE TYPE pool_status AS ENUM ('Active', 'Locked', 'Settled', 'Closed'); +EXCEPTION + WHEN duplicate_object THEN NULL; +END $$; + +ALTER TABLE pool ADD COLUMN status pool_status NOT NULL DEFAULT 'Active'; +CREATE INDEX idx_pool_status ON pool(status); + +CREATE TABLE user_pool ( + id SERIAL PRIMARY KEY, + user_id TEXT NOT NULL, + pool_id INTEGER REFERENCES pool(id), + amount_staked NUMERIC NOT NULL +); diff --git a/backend/migrations/20250730120511_create_validators.sql b/backend/migrations/20250730120511_create_validators.sql index e2b6701..39e579b 100644 --- a/backend/migrations/20250730120511_create_validators.sql +++ b/backend/migrations/20250730120511_create_validators.sql @@ -1,9 +1,9 @@ --- Add migration script here -CREATE TABLE IF NOT EXISTS validators ( - contract_address TEXT PRIMARY KEY, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - +-- Add migration script here +CREATE TABLE IF NOT EXISTS validators ( + contract_address TEXT PRIMARY KEY, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + CREATE INDEX IF NOT EXISTS idx_validators_contract_address ON validators(contract_address); \ No newline at end of file diff --git a/backend/scripts/run_tests.sh b/backend/scripts/run_tests.sh index 9290a3e..cc4b0f6 100755 --- a/backend/scripts/run_tests.sh +++ b/backend/scripts/run_tests.sh @@ -1,46 +1,46 @@ -#!/bin/bash - -# Exit on any error -set -e - -echo "πŸš€ Starting Predifi Backend Integration Tests" -echo "==============================================" - -# Check if Docker is running -if ! docker info > /dev/null 2>&1; then - echo "❌ Docker is not running. Please start Docker and try again." - exit 1 -fi - -# Function to cleanup on exit -cleanup() { - echo "🧹 Cleaning up test environment..." - docker-compose -f docker-compose.test.yml down -v - echo "βœ… Cleanup complete" -} - -# Set trap to cleanup on exit -trap cleanup EXIT - -# Start test database -echo "🐘 Starting test database..." -docker-compose -f docker-compose.test.yml up -d test-db - -# Wait for database to be ready -echo "⏳ Waiting for database to be ready..." -until docker-compose -f docker-compose.test.yml exec -T test-db pg_isready -U test_user -d test_db; do - echo "Database not ready yet, waiting..." - sleep 2 -done - -echo "βœ… Database is ready!" - -# Set test environment variables -export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" -export RUST_LOG=info - -# Run the tests -echo "πŸ§ͺ Running integration tests..." -cargo test --tests -- --nocapture - +#!/bin/bash + +# Exit on any error +set -e + +echo "πŸš€ Starting Predifi Backend Integration Tests" +echo "==============================================" + +# Check if Docker is running +if ! docker info > /dev/null 2>&1; then + echo "❌ Docker is not running. Please start Docker and try again." + exit 1 +fi + +# Function to cleanup on exit +cleanup() { + echo "🧹 Cleaning up test environment..." + docker-compose -f docker-compose.test.yml down -v + echo "βœ… Cleanup complete" +} + +# Set trap to cleanup on exit +trap cleanup EXIT + +# Start test database +echo "🐘 Starting test database..." +docker-compose -f docker-compose.test.yml up -d test-db + +# Wait for database to be ready +echo "⏳ Waiting for database to be ready..." +until docker-compose -f docker-compose.test.yml exec -T test-db pg_isready -U test_user -d test_db; do + echo "Database not ready yet, waiting..." + sleep 2 +done + +echo "βœ… Database is ready!" + +# Set test environment variables +export TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5433/test_db" +export RUST_LOG=info + +# Run the tests +echo "πŸ§ͺ Running integration tests..." +cargo test --tests -- --nocapture + echo "βœ… All tests completed successfully!" \ No newline at end of file diff --git a/backend/scripts/validate-local.sh b/backend/scripts/validate-local.sh index db0d862..0003c16 100755 --- a/backend/scripts/validate-local.sh +++ b/backend/scripts/validate-local.sh @@ -1,121 +1,121 @@ -#!/bin/bash - -# Predifi Backend Local Validation Script -# This script runs the same checks as the CI pipeline locally - -set -e - -echo "πŸš€ Predifi Backend Local Validation" -echo "==================================" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Function to print colored output -print_status() { - local status=$1 - local message=$2 - if [ "$status" = "success" ]; then - echo -e "${GREEN}βœ…${NC} $message" - elif [ "$status" = "error" ]; then - echo -e "${RED}❌${NC} $message" - elif [ "$status" = "warning" ]; then - echo -e "${YELLOW}⚠️${NC} $message" - fi -} - -# Function to check if command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Check prerequisites -echo "πŸ” Checking prerequisites..." - -if ! command_exists cargo; then - print_status "error" "Rust/Cargo not found. Please install Rust first." - exit 1 -fi - -if ! command_exists sqlx; then - print_status "warning" "SQLx CLI not found. Installing..." - cargo install sqlx-cli --no-default-features --features postgres -fi - -print_status "success" "Prerequisites check completed" - -# Check if DATABASE_URL is set -if [ -z "$DATABASE_URL" ]; then - print_status "warning" "DATABASE_URL not set. Using default test database..." - export DATABASE_URL="postgres://postgres:postgres@localhost:5432/testdb" -fi - -# Run code quality checks -echo "" -echo "πŸ” Running code quality checks..." - -echo "Running clippy..." -if cargo clippy -- -D warnings; then - print_status "success" "Clippy checks passed" -else - print_status "error" "Clippy checks failed" - exit 1 -fi - -echo "Checking code format..." -if cargo fmt -- --check; then - print_status "success" "Code format check passed" -else - print_status "error" "Code format check failed" - print_status "warning" "Run 'cargo fmt' to fix formatting issues" - exit 1 -fi - -# Run tests -echo "" -echo "πŸ§ͺ Running tests..." - -echo "Running unit tests..." -if cargo test --lib --bins; then - print_status "success" "Unit tests passed" -else - print_status "error" "Unit tests failed" - exit 1 -fi - -echo "Running integration tests..." -if cargo test --tests -- --nocapture; then - print_status "success" "Integration tests passed" -else - print_status "error" "Integration tests failed" - exit 1 -fi - -# Build release version -echo "" -echo "πŸ—οΈ Building release version..." -if cargo build --release; then - print_status "success" "Release build successful" -else - print_status "error" "Release build failed" - exit 1 -fi - -# Final summary -echo "" -echo "πŸŽ‰ Validation Summary" -echo "====================" -print_status "success" "All checks passed!" -print_status "success" "Ready to push to GitHub" -print_status "success" "CI pipeline should pass automatically" - -echo "" -echo "πŸ’‘ Next steps:" -echo " git add ." -echo " git commit -m 'Your commit message'" -echo " git push origin your-branch" -echo "" +#!/bin/bash + +# Predifi Backend Local Validation Script +# This script runs the same checks as the CI pipeline locally + +set -e + +echo "πŸš€ Predifi Backend Local Validation" +echo "==================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + local status=$1 + local message=$2 + if [ "$status" = "success" ]; then + echo -e "${GREEN}βœ…${NC} $message" + elif [ "$status" = "error" ]; then + echo -e "${RED}❌${NC} $message" + elif [ "$status" = "warning" ]; then + echo -e "${YELLOW}⚠️${NC} $message" + fi +} + +# Function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Check prerequisites +echo "πŸ” Checking prerequisites..." + +if ! command_exists cargo; then + print_status "error" "Rust/Cargo not found. Please install Rust first." + exit 1 +fi + +if ! command_exists sqlx; then + print_status "warning" "SQLx CLI not found. Installing..." + cargo install sqlx-cli --no-default-features --features postgres +fi + +print_status "success" "Prerequisites check completed" + +# Check if DATABASE_URL is set +if [ -z "$DATABASE_URL" ]; then + print_status "warning" "DATABASE_URL not set. Using default test database..." + export DATABASE_URL="postgres://postgres:postgres@localhost:5432/testdb" +fi + +# Run code quality checks +echo "" +echo "πŸ” Running code quality checks..." + +echo "Running clippy..." +if cargo clippy -- -D warnings; then + print_status "success" "Clippy checks passed" +else + print_status "error" "Clippy checks failed" + exit 1 +fi + +echo "Checking code format..." +if cargo fmt -- --check; then + print_status "success" "Code format check passed" +else + print_status "error" "Code format check failed" + print_status "warning" "Run 'cargo fmt' to fix formatting issues" + exit 1 +fi + +# Run tests +echo "" +echo "πŸ§ͺ Running tests..." + +echo "Running unit tests..." +if cargo test --lib --bins; then + print_status "success" "Unit tests passed" +else + print_status "error" "Unit tests failed" + exit 1 +fi + +echo "Running integration tests..." +if cargo test --tests -- --nocapture; then + print_status "success" "Integration tests passed" +else + print_status "error" "Integration tests failed" + exit 1 +fi + +# Build release version +echo "" +echo "πŸ—οΈ Building release version..." +if cargo build --release; then + print_status "success" "Release build successful" +else + print_status "error" "Release build failed" + exit 1 +fi + +# Final summary +echo "" +echo "πŸŽ‰ Validation Summary" +echo "====================" +print_status "success" "All checks passed!" +print_status "success" "Ready to push to GitHub" +print_status "success" "CI pipeline should pass automatically" + +echo "" +echo "πŸ’‘ Next steps:" +echo " git add ." +echo " git commit -m 'Your commit message'" +echo " git push origin your-branch" +echo "" echo "The CI pipeline will automatically run all tests and validations." \ No newline at end of file diff --git a/backend/src/config/db_config.rs b/backend/src/config/db_config.rs index 3931c67..1968c58 100644 --- a/backend/src/config/db_config.rs +++ b/backend/src/config/db_config.rs @@ -1,23 +1,23 @@ -use std::env; - -#[derive(Debug, Clone)] -pub struct DbConfig { - pub url: String, - pub max_connections: u32, -} - -impl DbConfig { - pub fn from_env() -> Self { - dotenv::dotenv().ok(); - Self { - url: Self::get_env_var("DATABASE_URL"), - max_connections: Self::get_env_var("DATABASE_MAX_CONNECTIONS") - .parse() - .unwrap_or(5), - } - } - - fn get_env_var(key: &str) -> String { - env::var(key).unwrap_or_else(|_| panic!("{key} must be set")) - } -} +use std::env; + +#[derive(Debug, Clone)] +pub struct DbConfig { + pub url: String, + pub max_connections: u32, +} + +impl DbConfig { + pub fn from_env() -> Self { + dotenv::dotenv().ok(); + Self { + url: Self::get_env_var("DATABASE_URL"), + max_connections: Self::get_env_var("DATABASE_MAX_CONNECTIONS") + .parse() + .unwrap_or(5), + } + } + + fn get_env_var(key: &str) -> String { + env::var(key).unwrap_or_else(|_| panic!("{key} must be set")) + } +} diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 6268621..8d33f59 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -1,2 +1,2 @@ -pub mod db_config; -pub mod tracing; +pub mod db_config; +pub mod tracing; diff --git a/backend/src/config/tracing.rs b/backend/src/config/tracing.rs index 12a1ea3..07fdf26 100644 --- a/backend/src/config/tracing.rs +++ b/backend/src/config/tracing.rs @@ -1,134 +1,134 @@ -use opentelemetry::{KeyValue, global, trace::TracerProvider}; -use opentelemetry_otlp::WithExportConfig; -use opentelemetry_sdk::{Resource, runtime, trace}; -use opentelemetry_stdout::SpanExporter; -use std::env; -use tracing_subscriber::{ - EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, util::SubscriberInitExt, -}; - -#[derive(Debug, Clone)] -pub struct TracingConfig { - pub service_name: String, - pub service_version: String, - pub environment: String, - pub otlp_endpoint: Option, - pub log_level: String, -} - -impl TracingConfig { - pub fn from_env() -> Self { - dotenv::dotenv().ok(); - - Self { - service_name: env::var("OTEL_SERVICE_NAME") - .unwrap_or_else(|_| "predifi-backend".to_string()), - service_version: env::var("OTEL_SERVICE_VERSION") - .unwrap_or_else(|_| "0.1.0".to_string()), - environment: env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()), - otlp_endpoint: env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(), - log_level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), - } - } -} - -pub fn init_tracing( - config: &TracingConfig, -) -> Result<(), Box> { - // Step 1: Initialize OpenTelemetry tracer - let resource = Resource::new(vec![ - KeyValue::new("service.name", config.service_name.clone()), - KeyValue::new("service.version", config.service_version.clone()), - KeyValue::new("environment", config.environment.clone()), - ]); - - let _tracer = if let Some(endpoint) = &config.otlp_endpoint { - // Use OTLP exporter for production - opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_endpoint(endpoint), - ) - .with_trace_config(trace::Config::default().with_resource(resource)) - .install_batch(runtime::Tokio)? - } else { - // Use stdout exporter for development - let exporter = SpanExporter::default(); - let provider = trace::TracerProvider::builder() - .with_span_processor( - trace::BatchSpanProcessor::builder(exporter, runtime::Tokio).build(), - ) - .with_config(trace::Config::default().with_resource(resource)) - .build(); - - let tracer = provider.tracer("predifi-backend"); - global::set_tracer_provider(provider); - tracer - }; - - // Step 2: Set up tracing subscriber with careful filtering to prevent circular dependencies - // Create a filter for the fmt layer that allows our application logs - let fmt_filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new(&config.log_level)) - .add_directive("h2=warn".parse().unwrap()) - .add_directive("hyper=warn".parse().unwrap()) - .add_directive("tonic=warn".parse().unwrap()) - .add_directive("tower=warn".parse().unwrap()); - - // DO NOT use OpenTelemetryLayer as it causes circular dependencies - // Instead, we'll manually inject trace context into logs - let subscriber = Registry::default().with( - fmt::layer() - .json() - .with_target(true) - .with_thread_ids(true) - .with_thread_names(true) - .with_file(true) - .with_line_number(true) - .with_current_span(true) - .with_span_list(true) - .with_filter(fmt_filter), - ); - - // Initialize the global subscriber WITH OpenTelemetry layer - subscriber.init(); - - // Log initialization success - tracing::info!( - service_name = config.service_name, - service_version = config.service_version, - environment = config.environment, - otlp_endpoint = config.otlp_endpoint.as_deref().unwrap_or("stdout"), - "OpenTelemetry tracing initialized successfully with automatic trace and span ID correlation" - ); - - Ok(()) -} - -pub fn shutdown_tracing() { - tracing::info!("Shutting down OpenTelemetry tracing"); - global::shutdown_tracer_provider(); -} - -// Function to get current OpenTelemetry trace context for manual injection -// This avoids the circular dependency by not going through the tracing layer -pub fn get_trace_context() -> Option<(String, String)> { - use opentelemetry::Context; - use opentelemetry::trace::TraceContextExt; - - // Get the current OpenTelemetry context directly (not through tracing) - let ctx = Context::current(); - let span = ctx.span(); - let span_context = span.span_context(); - - if span_context.is_valid() { - Some(( - span_context.trace_id().to_string(), - span_context.span_id().to_string(), - )) - } else { - None - } -} +use opentelemetry::{KeyValue, global, trace::TracerProvider}; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::{Resource, runtime, trace}; +use opentelemetry_stdout::SpanExporter; +use std::env; +use tracing_subscriber::{ + EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, util::SubscriberInitExt, +}; + +#[derive(Debug, Clone)] +pub struct TracingConfig { + pub service_name: String, + pub service_version: String, + pub environment: String, + pub otlp_endpoint: Option, + pub log_level: String, +} + +impl TracingConfig { + pub fn from_env() -> Self { + dotenv::dotenv().ok(); + + Self { + service_name: env::var("OTEL_SERVICE_NAME") + .unwrap_or_else(|_| "predifi-backend".to_string()), + service_version: env::var("OTEL_SERVICE_VERSION") + .unwrap_or_else(|_| "0.1.0".to_string()), + environment: env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()), + otlp_endpoint: env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok(), + log_level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), + } + } +} + +pub fn init_tracing( + config: &TracingConfig, +) -> Result<(), Box> { + // Step 1: Initialize OpenTelemetry tracer + let resource = Resource::new(vec![ + KeyValue::new("service.name", config.service_name.clone()), + KeyValue::new("service.version", config.service_version.clone()), + KeyValue::new("environment", config.environment.clone()), + ]); + + let _tracer = if let Some(endpoint) = &config.otlp_endpoint { + // Use OTLP exporter for production + opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(endpoint), + ) + .with_trace_config(trace::Config::default().with_resource(resource)) + .install_batch(runtime::Tokio)? + } else { + // Use stdout exporter for development + let exporter = SpanExporter::default(); + let provider = trace::TracerProvider::builder() + .with_span_processor( + trace::BatchSpanProcessor::builder(exporter, runtime::Tokio).build(), + ) + .with_config(trace::Config::default().with_resource(resource)) + .build(); + + let tracer = provider.tracer("predifi-backend"); + global::set_tracer_provider(provider); + tracer + }; + + // Step 2: Set up tracing subscriber with careful filtering to prevent circular dependencies + // Create a filter for the fmt layer that allows our application logs + let fmt_filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new(&config.log_level)) + .add_directive("h2=warn".parse().unwrap()) + .add_directive("hyper=warn".parse().unwrap()) + .add_directive("tonic=warn".parse().unwrap()) + .add_directive("tower=warn".parse().unwrap()); + + // DO NOT use OpenTelemetryLayer as it causes circular dependencies + // Instead, we'll manually inject trace context into logs + let subscriber = Registry::default().with( + fmt::layer() + .json() + .with_target(true) + .with_thread_ids(true) + .with_thread_names(true) + .with_file(true) + .with_line_number(true) + .with_current_span(true) + .with_span_list(true) + .with_filter(fmt_filter), + ); + + // Initialize the global subscriber WITH OpenTelemetry layer + subscriber.init(); + + // Log initialization success + tracing::info!( + service_name = config.service_name, + service_version = config.service_version, + environment = config.environment, + otlp_endpoint = config.otlp_endpoint.as_deref().unwrap_or("stdout"), + "OpenTelemetry tracing initialized successfully with automatic trace and span ID correlation" + ); + + Ok(()) +} + +pub fn shutdown_tracing() { + tracing::info!("Shutting down OpenTelemetry tracing"); + global::shutdown_tracer_provider(); +} + +// Function to get current OpenTelemetry trace context for manual injection +// This avoids the circular dependency by not going through the tracing layer +pub fn get_trace_context() -> Option<(String, String)> { + use opentelemetry::Context; + use opentelemetry::trace::TraceContextExt; + + // Get the current OpenTelemetry context directly (not through tracing) + let ctx = Context::current(); + let span = ctx.span(); + let span_context = span.span_context(); + + if span_context.is_valid() { + Some(( + span_context.trace_id().to_string(), + span_context.span_id().to_string(), + )) + } else { + None + } +} diff --git a/backend/src/controllers/market_controller.rs b/backend/src/controllers/market_controller.rs index 03b6dfc..e928ee8 100644 --- a/backend/src/controllers/market_controller.rs +++ b/backend/src/controllers/market_controller.rs @@ -1,173 +1,173 @@ -use crate::models::market::{Market, MarketCategory, MarketTag, MarketWithTags, NewMarket, Tag}; -use sqlx::{Pool, Postgres}; - -#[allow(dead_code)] -pub async fn create_market( - pool: &Pool, - name: &str, - description: Option<&str>, - category_id: Option, -) -> Result { - sqlx::query_as::<_, Market>( - "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING *", - ) - .bind(name) - .bind(description) - .bind(category_id) - .fetch_one(pool) - .await -} - -pub async fn create_market_with_tags( - pool: &Pool, - new_market: &NewMarket, -) -> Result { - let mut transaction = pool.begin().await?; - - // Insert market with all fields from Cairo contract - let market = sqlx::query_as::<_, Market>( - r#" - INSERT INTO market ( - name, description, category_id, image_url, event_source_url, - start_time, lock_time, end_time, option1, option2, - min_bet_amount, max_bet_amount, creator_fee, is_private, - creator_address, created_timestamp, status - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) - RETURNING * - "#, - ) - .bind(&new_market.name) - .bind(&new_market.description) - .bind(new_market.category_id) - .bind(&new_market.image_url) - .bind(&new_market.event_source_url) - .bind(new_market.start_time) - .bind(new_market.lock_time) - .bind(new_market.end_time) - .bind(&new_market.option1) - .bind(&new_market.option2) - .bind(&new_market.min_bet_amount) - .bind(&new_market.max_bet_amount) - .bind(new_market.creator_fee) - .bind(new_market.is_private) - .bind(&new_market.creator_address) - .bind(new_market.created_timestamp) - .bind(Some("active")) - .fetch_one(&mut *transaction) - .await?; - - let mut tags = Vec::new(); - - // Handle tags if provided - if let Some(tag_names) = &new_market.tags { - for tag_name in tag_names { - // Upsert tag (create if doesn't exist, get if exists) - let tag = sqlx::query_as::<_, Tag>( - r#" - INSERT INTO tags (name) VALUES ($1) - ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name - RETURNING * - "#, - ) - .bind(tag_name) - .fetch_one(&mut *transaction) - .await?; - - // Create market-tag association - sqlx::query_as::<_, MarketTag>( - r#" - INSERT INTO market_tags (market_id, tag_id) VALUES ($1, $2) - ON CONFLICT (market_id, tag_id) DO NOTHING - RETURNING * - "#, - ) - .bind(market.id) - .bind(tag.id) - .fetch_optional(&mut *transaction) - .await?; - - tags.push(tag); - } - } - - transaction.commit().await?; - - Ok(MarketWithTags { market, tags }) -} - -#[allow(dead_code)] -pub async fn get_market(pool: &Pool, id: i32) -> Result { - sqlx::query_as::<_, Market>("SELECT * FROM market WHERE id = $1") - .bind(id) - .fetch_one(pool) - .await -} - -pub async fn get_market_with_tags( - pool: &Pool, - id: i32, -) -> Result { - let market = get_market(pool, id).await?; - - let tags = sqlx::query_as::<_, Tag>( - r#" - SELECT t.* FROM tags t - INNER JOIN market_tags mt ON t.id = mt.tag_id - WHERE mt.market_id = $1 - "#, - ) - .bind(id) - .fetch_all(pool) - .await?; - - Ok(MarketWithTags { market, tags }) -} - -#[allow(dead_code)] -pub async fn create_market_category( - pool: &Pool, - name: &str, -) -> Result { - sqlx::query_as::<_, MarketCategory>( - "INSERT INTO market_category (name) VALUES ($1) RETURNING *", - ) - .bind(name) - .fetch_one(pool) - .await -} - -#[allow(dead_code)] -pub async fn get_market_category( - pool: &Pool, - id: i32, -) -> Result { - sqlx::query_as::<_, MarketCategory>("SELECT * FROM market_category WHERE id = $1") - .bind(id) - .fetch_one(pool) - .await -} - -#[allow(dead_code)] -pub async fn get_all_tags(pool: &Pool) -> Result, sqlx::Error> { - sqlx::query_as::<_, Tag>("SELECT * FROM tags ORDER BY name") - .fetch_all(pool) - .await -} - -#[allow(dead_code)] -pub async fn get_tags_by_market_id( - pool: &Pool, - market_id: i32, -) -> Result, sqlx::Error> { - sqlx::query_as::<_, Tag>( - r#" - SELECT t.* FROM tags t - INNER JOIN market_tags mt ON t.id = mt.tag_id - WHERE mt.market_id = $1 - ORDER BY t.name - "#, - ) - .bind(market_id) - .fetch_all(pool) - .await -} +use crate::models::market::{Market, MarketCategory, MarketTag, MarketWithTags, NewMarket, Tag}; +use sqlx::{Pool, Postgres}; + +#[allow(dead_code)] +pub async fn create_market( + pool: &Pool, + name: &str, + description: Option<&str>, + category_id: Option, +) -> Result { + sqlx::query_as::<_, Market>( + "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING *", + ) + .bind(name) + .bind(description) + .bind(category_id) + .fetch_one(pool) + .await +} + +pub async fn create_market_with_tags( + pool: &Pool, + new_market: &NewMarket, +) -> Result { + let mut transaction = pool.begin().await?; + + // Insert market with all fields from Cairo contract + let market = sqlx::query_as::<_, Market>( + r#" + INSERT INTO market ( + name, description, category_id, image_url, event_source_url, + start_time, lock_time, end_time, option1, option2, + min_bet_amount, max_bet_amount, creator_fee, is_private, + creator_address, created_timestamp, status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) + RETURNING * + "#, + ) + .bind(&new_market.name) + .bind(&new_market.description) + .bind(new_market.category_id) + .bind(&new_market.image_url) + .bind(&new_market.event_source_url) + .bind(new_market.start_time) + .bind(new_market.lock_time) + .bind(new_market.end_time) + .bind(&new_market.option1) + .bind(&new_market.option2) + .bind(&new_market.min_bet_amount) + .bind(&new_market.max_bet_amount) + .bind(new_market.creator_fee) + .bind(new_market.is_private) + .bind(&new_market.creator_address) + .bind(new_market.created_timestamp) + .bind(Some("active")) + .fetch_one(&mut *transaction) + .await?; + + let mut tags = Vec::new(); + + // Handle tags if provided + if let Some(tag_names) = &new_market.tags { + for tag_name in tag_names { + // Upsert tag (create if doesn't exist, get if exists) + let tag = sqlx::query_as::<_, Tag>( + r#" + INSERT INTO tags (name) VALUES ($1) + ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name + RETURNING * + "#, + ) + .bind(tag_name) + .fetch_one(&mut *transaction) + .await?; + + // Create market-tag association + sqlx::query_as::<_, MarketTag>( + r#" + INSERT INTO market_tags (market_id, tag_id) VALUES ($1, $2) + ON CONFLICT (market_id, tag_id) DO NOTHING + RETURNING * + "#, + ) + .bind(market.id) + .bind(tag.id) + .fetch_optional(&mut *transaction) + .await?; + + tags.push(tag); + } + } + + transaction.commit().await?; + + Ok(MarketWithTags { market, tags }) +} + +#[allow(dead_code)] +pub async fn get_market(pool: &Pool, id: i32) -> Result { + sqlx::query_as::<_, Market>("SELECT * FROM market WHERE id = $1") + .bind(id) + .fetch_one(pool) + .await +} + +pub async fn get_market_with_tags( + pool: &Pool, + id: i32, +) -> Result { + let market = get_market(pool, id).await?; + + let tags = sqlx::query_as::<_, Tag>( + r#" + SELECT t.* FROM tags t + INNER JOIN market_tags mt ON t.id = mt.tag_id + WHERE mt.market_id = $1 + "#, + ) + .bind(id) + .fetch_all(pool) + .await?; + + Ok(MarketWithTags { market, tags }) +} + +#[allow(dead_code)] +pub async fn create_market_category( + pool: &Pool, + name: &str, +) -> Result { + sqlx::query_as::<_, MarketCategory>( + "INSERT INTO market_category (name) VALUES ($1) RETURNING *", + ) + .bind(name) + .fetch_one(pool) + .await +} + +#[allow(dead_code)] +pub async fn get_market_category( + pool: &Pool, + id: i32, +) -> Result { + sqlx::query_as::<_, MarketCategory>("SELECT * FROM market_category WHERE id = $1") + .bind(id) + .fetch_one(pool) + .await +} + +#[allow(dead_code)] +pub async fn get_all_tags(pool: &Pool) -> Result, sqlx::Error> { + sqlx::query_as::<_, Tag>("SELECT * FROM tags ORDER BY name") + .fetch_all(pool) + .await +} + +#[allow(dead_code)] +pub async fn get_tags_by_market_id( + pool: &Pool, + market_id: i32, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, Tag>( + r#" + SELECT t.* FROM tags t + INNER JOIN market_tags mt ON t.id = mt.tag_id + WHERE mt.market_id = $1 + ORDER BY t.name + "#, + ) + .bind(market_id) + .fetch_all(pool) + .await +} diff --git a/backend/src/controllers/mod.rs b/backend/src/controllers/mod.rs index 41e57b5..902eb34 100644 --- a/backend/src/controllers/mod.rs +++ b/backend/src/controllers/mod.rs @@ -1,3 +1,3 @@ -pub mod market_controller; -pub mod pool_controller; -pub mod validator_controller; +pub mod market_controller; +pub mod pool_controller; +pub mod validator_controller; diff --git a/backend/src/controllers/pool_controller.rs b/backend/src/controllers/pool_controller.rs index 4bb60c5..14d1f7f 100644 --- a/backend/src/controllers/pool_controller.rs +++ b/backend/src/controllers/pool_controller.rs @@ -1,113 +1,113 @@ -use crate::models::pool::{NewPool, Pool, UserPool}; -use bigdecimal::BigDecimal; -use sqlx::{Pool as SqlxPool, Postgres}; - -#[allow(dead_code)] -pub async fn create_pool( - pool: &SqlxPool, - new_pool: &NewPool, -) -> Result { - sqlx::query_as::<_, Pool>( - "INSERT INTO pool (market_id, name, type, description, image_url, event_source_url, start_time, lock_time, end_time, option1, option2, min_bet_amount, max_bet_amount, creator_fee, is_private, category_id) VALUES \ - ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING *" - ) - .bind(new_pool.market_id) - .bind(&new_pool.name) - .bind(new_pool.r#type) - .bind(&new_pool.description) - .bind(&new_pool.image_url) - .bind(&new_pool.event_source_url) - .bind(new_pool.start_time) - .bind(new_pool.lock_time) - .bind(new_pool.end_time) - .bind(&new_pool.option1) - .bind(&new_pool.option2) - .bind(&new_pool.min_bet_amount) - .bind(&new_pool.max_bet_amount) - .bind(new_pool.creator_fee) - .bind(new_pool.is_private) - .bind(new_pool.category_id) - .fetch_one(pool) - .await -} - -pub async fn get_pools_by_status( - pool: &SqlxPool, - status: &str, - limit: i64, - offset: i64, -) -> Result, sqlx::Error> { - sqlx::query_as::<_, Pool>( - "SELECT * FROM pool WHERE status = $1::pool_status ORDER BY id LIMIT $2 OFFSET $3", - ) - .bind(status) - .bind(limit) - .bind(offset) - .fetch_all(pool) - .await -} - -pub async fn get_active_pools( - pool: &SqlxPool, - limit: i64, - offset: i64, -) -> Result, sqlx::Error> { - get_pools_by_status(pool, "Active", limit, offset).await -} - -pub async fn get_locked_pools( - pool: &SqlxPool, - limit: i64, - offset: i64, -) -> Result, sqlx::Error> { - get_pools_by_status(pool, "Locked", limit, offset).await -} - -pub async fn get_settled_pools( - pool: &SqlxPool, - limit: i64, - offset: i64, -) -> Result, sqlx::Error> { - get_pools_by_status(pool, "Settled", limit, offset).await -} - -pub async fn get_closed_pools( - pool: &SqlxPool, - limit: i64, - offset: i64, -) -> Result, sqlx::Error> { - get_pools_by_status(pool, "Closed", limit, offset).await -} - -#[allow(dead_code)] -pub async fn get_pool(pool: &SqlxPool, id: i32) -> Result { - sqlx::query_as::<_, Pool>("SELECT * FROM pool WHERE id = $1") - .bind(id) - .fetch_one(pool) - .await -} - -#[allow(dead_code)] -pub async fn create_user_pool( - pool: &SqlxPool, - user_id: &str, - pool_id: i32, - amount_staked: &BigDecimal, -) -> Result { - sqlx::query_as::<_, UserPool>( - "INSERT INTO user_pool (user_id, pool_id, amount_staked) VALUES ($1, $2, $3) RETURNING *", - ) - .bind(user_id) - .bind(pool_id) - .bind(amount_staked) - .fetch_one(pool) - .await -} - -#[allow(dead_code)] -pub async fn get_user_pool(pool: &SqlxPool, id: i32) -> Result { - sqlx::query_as::<_, UserPool>("SELECT * FROM user_pool WHERE id = $1") - .bind(id) - .fetch_one(pool) - .await -} +use crate::models::pool::{NewPool, Pool, UserPool}; +use bigdecimal::BigDecimal; +use sqlx::{Pool as SqlxPool, Postgres}; + +#[allow(dead_code)] +pub async fn create_pool( + pool: &SqlxPool, + new_pool: &NewPool, +) -> Result { + sqlx::query_as::<_, Pool>( + "INSERT INTO pool (market_id, name, type, description, image_url, event_source_url, start_time, lock_time, end_time, option1, option2, min_bet_amount, max_bet_amount, creator_fee, is_private, category_id) VALUES \ + ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING *" + ) + .bind(new_pool.market_id) + .bind(&new_pool.name) + .bind(new_pool.r#type) + .bind(&new_pool.description) + .bind(&new_pool.image_url) + .bind(&new_pool.event_source_url) + .bind(new_pool.start_time) + .bind(new_pool.lock_time) + .bind(new_pool.end_time) + .bind(&new_pool.option1) + .bind(&new_pool.option2) + .bind(&new_pool.min_bet_amount) + .bind(&new_pool.max_bet_amount) + .bind(new_pool.creator_fee) + .bind(new_pool.is_private) + .bind(new_pool.category_id) + .fetch_one(pool) + .await +} + +pub async fn get_pools_by_status( + pool: &SqlxPool, + status: &str, + limit: i64, + offset: i64, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, Pool>( + "SELECT * FROM pool WHERE status = $1::pool_status ORDER BY id LIMIT $2 OFFSET $3", + ) + .bind(status) + .bind(limit) + .bind(offset) + .fetch_all(pool) + .await +} + +pub async fn get_active_pools( + pool: &SqlxPool, + limit: i64, + offset: i64, +) -> Result, sqlx::Error> { + get_pools_by_status(pool, "Active", limit, offset).await +} + +pub async fn get_locked_pools( + pool: &SqlxPool, + limit: i64, + offset: i64, +) -> Result, sqlx::Error> { + get_pools_by_status(pool, "Locked", limit, offset).await +} + +pub async fn get_settled_pools( + pool: &SqlxPool, + limit: i64, + offset: i64, +) -> Result, sqlx::Error> { + get_pools_by_status(pool, "Settled", limit, offset).await +} + +pub async fn get_closed_pools( + pool: &SqlxPool, + limit: i64, + offset: i64, +) -> Result, sqlx::Error> { + get_pools_by_status(pool, "Closed", limit, offset).await +} + +#[allow(dead_code)] +pub async fn get_pool(pool: &SqlxPool, id: i32) -> Result { + sqlx::query_as::<_, Pool>("SELECT * FROM pool WHERE id = $1") + .bind(id) + .fetch_one(pool) + .await +} + +#[allow(dead_code)] +pub async fn create_user_pool( + pool: &SqlxPool, + user_id: &str, + pool_id: i32, + amount_staked: &BigDecimal, +) -> Result { + sqlx::query_as::<_, UserPool>( + "INSERT INTO user_pool (user_id, pool_id, amount_staked) VALUES ($1, $2, $3) RETURNING *", + ) + .bind(user_id) + .bind(pool_id) + .bind(amount_staked) + .fetch_one(pool) + .await +} + +#[allow(dead_code)] +pub async fn get_user_pool(pool: &SqlxPool, id: i32) -> Result { + sqlx::query_as::<_, UserPool>("SELECT * FROM user_pool WHERE id = $1") + .bind(id) + .fetch_one(pool) + .await +} diff --git a/backend/src/controllers/validator_controller.rs b/backend/src/controllers/validator_controller.rs index 6c525ec..09d9736 100644 --- a/backend/src/controllers/validator_controller.rs +++ b/backend/src/controllers/validator_controller.rs @@ -1,50 +1,50 @@ -use crate::error::{AppError, AppResult}; -use crate::models::validator::Validator; -use sqlx::{Pool, Postgres}; - -fn validate_contract_address(address: &str) -> Result<(), AppError> { - let is_valid = address.starts_with("0x") - && address.len() == 42 - && address.chars().all(|c| c.is_ascii_hexdigit() || c == 'x'); - if !is_valid { - Err(AppError::BadRequest( - "Invalid contract address format".into(), - )) - } else { - Ok(()) - } -} - -pub async fn get_validator(pool: &Pool, address: &str) -> AppResult { - validate_contract_address(address)?; - let validator = sqlx::query_as::<_, Validator>( - "SELECT * FROM validators WHERE LOWER(contract_address) = LOWER($1)", - ) - .bind(address) - .fetch_optional(pool) - .await - .map_err(AppError::Database)?; - - validator.ok_or_else(|| AppError::NotFound("Validator not found".into())) -} - -pub async fn get_validators(pool: &Pool) -> AppResult> { - let validators = sqlx::query_as::<_, Validator>("SELECT * FROM validators") - .fetch_all(pool) - .await - .map_err(AppError::Database)?; - Ok(validators) -} - -pub async fn get_validator_status(pool: &Pool, address: &str) -> AppResult { - validate_contract_address(address)?; - let status = sqlx::query_scalar::<_, bool>( - "SELECT is_active FROM validators WHERE LOWER(contract_address) = LOWER($1)", - ) - .bind(address) - .fetch_optional(pool) - .await - .map_err(AppError::Database)?; - - status.ok_or_else(|| AppError::NotFound("Validator not found".into())) -} +use crate::error::{AppError, AppResult}; +use crate::models::validator::Validator; +use sqlx::{Pool, Postgres}; + +fn validate_contract_address(address: &str) -> Result<(), AppError> { + let is_valid = address.starts_with("0x") + && address.len() == 42 + && address.chars().all(|c| c.is_ascii_hexdigit() || c == 'x'); + if !is_valid { + Err(AppError::BadRequest( + "Invalid contract address format".into(), + )) + } else { + Ok(()) + } +} + +pub async fn get_validator(pool: &Pool, address: &str) -> AppResult { + validate_contract_address(address)?; + let validator = sqlx::query_as::<_, Validator>( + "SELECT * FROM validators WHERE LOWER(contract_address) = LOWER($1)", + ) + .bind(address) + .fetch_optional(pool) + .await + .map_err(AppError::Database)?; + + validator.ok_or_else(|| AppError::NotFound("Validator not found".into())) +} + +pub async fn get_validators(pool: &Pool) -> AppResult> { + let validators = sqlx::query_as::<_, Validator>("SELECT * FROM validators") + .fetch_all(pool) + .await + .map_err(AppError::Database)?; + Ok(validators) +} + +pub async fn get_validator_status(pool: &Pool, address: &str) -> AppResult { + validate_contract_address(address)?; + let status = sqlx::query_scalar::<_, bool>( + "SELECT is_active FROM validators WHERE LOWER(contract_address) = LOWER($1)", + ) + .bind(address) + .fetch_optional(pool) + .await + .map_err(AppError::Database)?; + + status.ok_or_else(|| AppError::NotFound("Validator not found".into())) +} diff --git a/backend/src/db/database.rs b/backend/src/db/database.rs index 6f1c23b..d286d51 100644 --- a/backend/src/db/database.rs +++ b/backend/src/db/database.rs @@ -1,114 +1,114 @@ -#[derive(Clone)] -pub struct AppState { - pub db: Database, -} -use crate::config::db_config::DbConfig; - -use sqlx::{Pool, Postgres}; -use std::time::Instant; -use tracing::Instrument; - -#[derive(Clone)] -pub struct Database { - pub pool: Pool, -} - -impl Database { - /// Expose a reference to the connection pool for migrations and queries - pub fn pool(&self) -> &Pool { - &self.pool - } - - /// Create a Database instance from an existing pool (useful for testing) - #[allow(dead_code)] - pub fn from_pool(pool: Pool) -> Self { - Self { pool } - } -} - -impl Database { - pub async fn connect(config: &DbConfig) -> Self { - let connect_span = tracing::info_span!( - "database_connect", - database.type = "postgresql", - database.max_connections = config.max_connections, - ); - - let pool = async { - tracing::info!( - event = "database_connection_start", - database.max_connections = config.max_connections, - "Starting database connection" - ); - - let pool = sqlx::postgres::PgPoolOptions::new() - .max_connections(config.max_connections) - .connect(&config.url) - .await - .expect("Failed to connect to Postgres"); - - tracing::info!( - event = "database_connection_pool_created", - database.max_connections = config.max_connections, - "Database connection pool created successfully" - ); - - pool - } - .instrument(connect_span) - .await; - - Self { pool } - } - - pub async fn ping(&self) -> Result { - let ping_span = tracing::info_span!( - "database_ping", - database.operation = "ping", - database.query = "SELECT 1", - ); - - async { - let start_time = Instant::now(); - - tracing::debug!( - event = "database_query_start", - database.operation = "ping", - database.query = "SELECT 1", - "Starting database ping query" - ); - - let result: Result<(i32,), sqlx::Error> = - sqlx::query_as("SELECT 1").fetch_one(&self.pool).await; - - let duration = start_time.elapsed(); - - match result { - Ok((value,)) => { - tracing::info!( - event = "database_query_success", - database.operation = "ping", - database.query = "SELECT 1", - database.result = value, - database.duration_ms = duration.as_millis().to_string(), - "Database ping successful" - ); - Ok(value) - } - Err(e) => { - tracing::error!( - event = "database_query_error", - database.operation = "ping", - database.query = "SELECT 1", - database.duration_ms = duration.as_millis().to_string(), - error = %e, - "Database ping failed" - ); - Err(e) - } - } - } - .instrument(ping_span) - .await - } -} +#[derive(Clone)] +pub struct AppState { + pub db: Database, +} +use crate::config::db_config::DbConfig; + +use sqlx::{Pool, Postgres}; +use std::time::Instant; +use tracing::Instrument; + +#[derive(Clone)] +pub struct Database { + pub pool: Pool, +} + +impl Database { + /// Expose a reference to the connection pool for migrations and queries + pub fn pool(&self) -> &Pool { + &self.pool + } + + /// Create a Database instance from an existing pool (useful for testing) + #[allow(dead_code)] + pub fn from_pool(pool: Pool) -> Self { + Self { pool } + } +} + +impl Database { + pub async fn connect(config: &DbConfig) -> Self { + let connect_span = tracing::info_span!( + "database_connect", + database.type = "postgresql", + database.max_connections = config.max_connections, + ); + + let pool = async { + tracing::info!( + event = "database_connection_start", + database.max_connections = config.max_connections, + "Starting database connection" + ); + + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(config.max_connections) + .connect(&config.url) + .await + .expect("Failed to connect to Postgres"); + + tracing::info!( + event = "database_connection_pool_created", + database.max_connections = config.max_connections, + "Database connection pool created successfully" + ); + + pool + } + .instrument(connect_span) + .await; + + Self { pool } + } + + pub async fn ping(&self) -> Result { + let ping_span = tracing::info_span!( + "database_ping", + database.operation = "ping", + database.query = "SELECT 1", + ); + + async { + let start_time = Instant::now(); + + tracing::debug!( + event = "database_query_start", + database.operation = "ping", + database.query = "SELECT 1", + "Starting database ping query" + ); + + let result: Result<(i32,), sqlx::Error> = + sqlx::query_as("SELECT 1").fetch_one(&self.pool).await; + + let duration = start_time.elapsed(); + + match result { + Ok((value,)) => { + tracing::info!( + event = "database_query_success", + database.operation = "ping", + database.query = "SELECT 1", + database.result = value, + database.duration_ms = duration.as_millis().to_string(), + "Database ping successful" + ); + Ok(value) + } + Err(e) => { + tracing::error!( + event = "database_query_error", + database.operation = "ping", + database.query = "SELECT 1", + database.duration_ms = duration.as_millis().to_string(), + error = %e, + "Database ping failed" + ); + Err(e) + } + } + } + .instrument(ping_span) + .await + } +} diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index 8fd0a6b..fbfd150 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -1 +1 @@ -pub mod database; +pub mod database; diff --git a/backend/src/error.rs b/backend/src/error.rs index 87c74b0..d959103 100644 --- a/backend/src/error.rs +++ b/backend/src/error.rs @@ -1,194 +1,194 @@ -use axum::response::Response; -use axum::{ - http::StatusCode, - response::{IntoResponse, Json}, -}; -use serde_json::json; -use std::fmt; - -pub type AppResult = Result; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum AppError { - Database(sqlx::Error), - Configuration(String), - Internal(String), - BadRequest(String), - NotFound(String), - Unauthorized(String), - Forbidden(String), - ServiceUnavailable(String), -} - -impl fmt::Display for AppError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AppError::Database(e) => write!(f, "Database error: {e}"), - AppError::Configuration(msg) => write!(f, "Configuration error: {msg}"), - AppError::Internal(msg) => write!(f, "Internal error: {msg}"), - AppError::BadRequest(msg) => write!(f, "Bad request: {msg}"), - AppError::NotFound(msg) => write!(f, "Not found: {msg}"), - AppError::Unauthorized(msg) => write!(f, "Unauthorized: {msg}"), - AppError::Forbidden(msg) => write!(f, "Forbidden: {msg}"), - AppError::ServiceUnavailable(msg) => write!(f, "Service unavailable: {msg}"), - } - } -} - -impl std::error::Error for AppError {} - -impl From for AppError { - fn from(err: sqlx::Error) -> Self { - tracing::error!( - event = "database_error_conversion", - error = %err, - error.type = "sqlx::Error", - "Converting sqlx::Error to AppError" - ); - AppError::Database(err) - } -} - -impl IntoResponse for AppError { - fn into_response(self) -> Response { - let (status, error_message) = match &self { - AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"), - AppError::Configuration(_) => { - (StatusCode::INTERNAL_SERVER_ERROR, "Configuration error") - } - AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"), - AppError::BadRequest(_) => (StatusCode::BAD_REQUEST, "Bad request"), - AppError::NotFound(_) => (StatusCode::NOT_FOUND, "Not found"), - AppError::Unauthorized(_) => (StatusCode::UNAUTHORIZED, "Unauthorized"), - AppError::Forbidden(_) => (StatusCode::FORBIDDEN, "Forbidden"), - AppError::ServiceUnavailable(_) => { - (StatusCode::SERVICE_UNAVAILABLE, "Service unavailable") - } - }; - - // Log the error with structured context - match &self { - AppError::Database(e) => { - tracing::error!( - event = "application_error", - error.type = "database", - error.message = %e, - http.status_code = status.as_u16(), - "Database error occurred" - ); - } - AppError::Configuration(msg) => { - tracing::error!( - event = "application_error", - error.type = "configuration", - error.message = msg, - http.status_code = status.as_u16(), - "Configuration error occurred" - ); - } - AppError::Internal(msg) => { - tracing::error!( - event = "application_error", - error.type = "internal", - error.message = msg, - http.status_code = status.as_u16(), - "Internal error occurred" - ); - } - AppError::BadRequest(msg) => { - tracing::warn!( - event = "application_error", - error.type = "bad_request", - error.message = msg, - http.status_code = status.as_u16(), - "Bad request error" - ); - } - AppError::NotFound(msg) => { - tracing::warn!( - event = "application_error", - error.type = "not_found", - error.message = msg, - http.status_code = status.as_u16(), - "Not found error" - ); - } - AppError::Unauthorized(msg) => { - tracing::warn!( - event = "application_error", - error.type = "unauthorized", - error.message = msg, - http.status_code = status.as_u16(), - "Unauthorized error" - ); - } - AppError::Forbidden(msg) => { - tracing::warn!( - event = "application_error", - error.type = "forbidden", - error.message = msg, - http.status_code = status.as_u16(), - "Forbidden error" - ); - } - AppError::ServiceUnavailable(msg) => { - tracing::error!( - event = "application_error", - error.type = "service_unavailable", - error.message = msg, - http.status_code = status.as_u16(), - "Service unavailable error" - ); - } - } - - let body = Json(json!({ - "error": error_message, - "status": status.as_u16(), - })); - - (status, body).into_response() - } -} - -// Helper functions for creating errors with structured logging -impl AppError { - pub fn database_with_context(e: sqlx::Error, context: &str) -> Self { - tracing::error!( - event = "error_creation", - error.type = "database", - error.context = context, - error.source = %e, - "Creating database error with context" - ); - - AppError::Database(e) - } - - #[allow(dead_code)] - pub fn internal_with_context(message: &str, context: &str) -> Self { - tracing::error!( - event = "error_creation", - error.type = "internal", - error.context = context, - error.message = message, - "Creating internal error with context" - ); - - AppError::Internal(format!("{context}: {message}")) - } - - #[allow(dead_code)] - pub fn config_with_context(message: &str, context: &str) -> Self { - tracing::error!( - event = "error_creation", - error.type = "configuration", - error.context = context, - error.message = message, - "Creating configuration error with context" - ); - - AppError::Configuration(format!("{context}: {message}")) - } -} +use axum::response::Response; +use axum::{ + http::StatusCode, + response::{IntoResponse, Json}, +}; +use serde_json::json; +use std::fmt; + +pub type AppResult = Result; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum AppError { + Database(sqlx::Error), + Configuration(String), + Internal(String), + BadRequest(String), + NotFound(String), + Unauthorized(String), + Forbidden(String), + ServiceUnavailable(String), +} + +impl fmt::Display for AppError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AppError::Database(e) => write!(f, "Database error: {e}"), + AppError::Configuration(msg) => write!(f, "Configuration error: {msg}"), + AppError::Internal(msg) => write!(f, "Internal error: {msg}"), + AppError::BadRequest(msg) => write!(f, "Bad request: {msg}"), + AppError::NotFound(msg) => write!(f, "Not found: {msg}"), + AppError::Unauthorized(msg) => write!(f, "Unauthorized: {msg}"), + AppError::Forbidden(msg) => write!(f, "Forbidden: {msg}"), + AppError::ServiceUnavailable(msg) => write!(f, "Service unavailable: {msg}"), + } + } +} + +impl std::error::Error for AppError {} + +impl From for AppError { + fn from(err: sqlx::Error) -> Self { + tracing::error!( + event = "database_error_conversion", + error = %err, + error.type = "sqlx::Error", + "Converting sqlx::Error to AppError" + ); + AppError::Database(err) + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let (status, error_message) = match &self { + AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"), + AppError::Configuration(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, "Configuration error") + } + AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"), + AppError::BadRequest(_) => (StatusCode::BAD_REQUEST, "Bad request"), + AppError::NotFound(_) => (StatusCode::NOT_FOUND, "Not found"), + AppError::Unauthorized(_) => (StatusCode::UNAUTHORIZED, "Unauthorized"), + AppError::Forbidden(_) => (StatusCode::FORBIDDEN, "Forbidden"), + AppError::ServiceUnavailable(_) => { + (StatusCode::SERVICE_UNAVAILABLE, "Service unavailable") + } + }; + + // Log the error with structured context + match &self { + AppError::Database(e) => { + tracing::error!( + event = "application_error", + error.type = "database", + error.message = %e, + http.status_code = status.as_u16(), + "Database error occurred" + ); + } + AppError::Configuration(msg) => { + tracing::error!( + event = "application_error", + error.type = "configuration", + error.message = msg, + http.status_code = status.as_u16(), + "Configuration error occurred" + ); + } + AppError::Internal(msg) => { + tracing::error!( + event = "application_error", + error.type = "internal", + error.message = msg, + http.status_code = status.as_u16(), + "Internal error occurred" + ); + } + AppError::BadRequest(msg) => { + tracing::warn!( + event = "application_error", + error.type = "bad_request", + error.message = msg, + http.status_code = status.as_u16(), + "Bad request error" + ); + } + AppError::NotFound(msg) => { + tracing::warn!( + event = "application_error", + error.type = "not_found", + error.message = msg, + http.status_code = status.as_u16(), + "Not found error" + ); + } + AppError::Unauthorized(msg) => { + tracing::warn!( + event = "application_error", + error.type = "unauthorized", + error.message = msg, + http.status_code = status.as_u16(), + "Unauthorized error" + ); + } + AppError::Forbidden(msg) => { + tracing::warn!( + event = "application_error", + error.type = "forbidden", + error.message = msg, + http.status_code = status.as_u16(), + "Forbidden error" + ); + } + AppError::ServiceUnavailable(msg) => { + tracing::error!( + event = "application_error", + error.type = "service_unavailable", + error.message = msg, + http.status_code = status.as_u16(), + "Service unavailable error" + ); + } + } + + let body = Json(json!({ + "error": error_message, + "status": status.as_u16(), + })); + + (status, body).into_response() + } +} + +// Helper functions for creating errors with structured logging +impl AppError { + pub fn database_with_context(e: sqlx::Error, context: &str) -> Self { + tracing::error!( + event = "error_creation", + error.type = "database", + error.context = context, + error.source = %e, + "Creating database error with context" + ); + + AppError::Database(e) + } + + #[allow(dead_code)] + pub fn internal_with_context(message: &str, context: &str) -> Self { + tracing::error!( + event = "error_creation", + error.type = "internal", + error.context = context, + error.message = message, + "Creating internal error with context" + ); + + AppError::Internal(format!("{context}: {message}")) + } + + #[allow(dead_code)] + pub fn config_with_context(message: &str, context: &str) -> Self { + tracing::error!( + event = "error_creation", + error.type = "configuration", + error.context = context, + error.message = message, + "Creating configuration error with context" + ); + + AppError::Configuration(format!("{context}: {message}")) + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index d9844d7..c0f294c 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,16 +1,16 @@ -pub mod config; -pub mod controllers; -pub mod db; -pub mod error; -pub mod middleware; -pub mod models; -pub mod routes; - -pub use error::{AppError, AppResult}; - -use crate::db::database::Database; - -#[derive(Clone)] -pub struct AppState { - pub db: Database, -} +pub mod config; +pub mod controllers; +pub mod db; +pub mod error; +pub mod middleware; +pub mod models; +pub mod routes; + +pub use error::{AppError, AppResult}; + +use crate::db::database::Database; + +#[derive(Clone)] +pub struct AppState { + pub db: Database, +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 5606849..817c900 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,271 +1,271 @@ -mod config; -mod controllers; -mod db; -pub mod error; -mod middleware; -mod models; -mod routes; - -use axum::{ - Router, - extract::{Extension, State}, - http::StatusCode, - response::IntoResponse, - routing::{get, post}, -}; -use middleware::request_id_middleware; -use routes::pool_route::pool_routes; -use routes::validator_route::validator_routes; -use std::net::SocketAddr; -use tracing::Instrument; - -use config::db_config::DbConfig; -use config::tracing::{TracingConfig, get_trace_context, init_tracing, shutdown_tracing}; -use db::database::AppState; -use db::database::Database; -use error::{AppError, AppResult}; -use middleware::RequestId; -use routes::market::{create_market_handler, get_market_handler}; - -#[tokio::main] -async fn main() -> Result<(), AppError> { - // Initialize structured logging with OpenTelemetry - let tracing_config = TracingConfig::from_env(); - init_tracing(&tracing_config).map_err(|e| { - eprintln!("Failed to initialize tracing: {e}"); - AppError::Internal(format!("Tracing initialization failed: {e}")) - })?; - - let config = DbConfig::from_env(); - let db = Database::connect(&config).await; - // Run SQLX migrations after connecting to the database - sqlx::migrate!("./migrations") - .run(db.pool()) - .await - .expect("Failed to run migrations"); - - // Check DB connection at startup with structured logging - match db.ping().await { - Ok(_) => { - tracing::info!( - event = "database_connection_success", - database.type = "postgresql", - "Successfully connected to PostgreSQL database" - ); - } - Err(e) => { - tracing::error!( - event = "database_connection_failed", - database.type = "postgresql", - error = %e, - "Failed to connect to database" - ); - shutdown_tracing(); - return Err(AppError::Internal(format!( - "Database connection failed: {e}" - ))); - } - } - - let state = AppState { db }; - - let app = Router::new() - .route("/ping", get(ping_handler)) - .route("/health", get(health_handler)) - .route("/markets", post(create_market_handler)) - .route("/markets/:id", get(get_market_handler)) - .merge(pool_routes()) // Merge the new pool routes - .merge(validator_routes()) - .with_state(state) - .layer(request_id_middleware()); - - let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); - tracing::info!( - event = "server_starting", - server.address = %addr, - server.port = 3000, - "Starting server with manual OpenTelemetry trace correlation" - ); - - let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - - // Set up graceful shutdown - let server = axum::serve(listener, app); - - // Handle shutdown signal - tokio::select! { - result = server => { - if let Err(e) = result { - tracing::error!( - event = "server_error", - error = %e, - "Server error" - ); - } - } - _ = tokio::signal::ctrl_c() => { - tracing::info!( - event = "shutdown_signal_received", - "Received shutdown signal" - ); - } - } - - tracing::info!(event = "server_shutdown", "Server shutdown complete"); - shutdown_tracing(); - Ok(()) -} - -async fn ping_handler( - State(state): State, - Extension(request_id): Extension, -) -> AppResult { - let request_id = request_id.as_str(); - - // Note: OpenTelemetry span creation removed due to dependency version conflicts - - let span = tracing::info_span!( - "ping_handler", - http.method = "GET", - http.route = "/ping", - request_id = request_id, - ); - - async move { - // Get trace context for structured logging - let (trace_id, span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::info!( - event = "http_request_start", - http.method = "GET", - http.route = "/ping", - request_id = request_id, - otel.trace_id = trace_id, - otel.span_id = span_id, - "Processing ping request with OpenTelemetry correlation" - ); - - let start_time = std::time::Instant::now(); - - match state.db.ping().await { - Ok(val) => { - let response = format!("pong: {val}"); - let (success_trace_id, success_span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::info!( - event = "http_request_success", - http.method = "GET", - http.route = "/ping", - http.status_code = 200, - http.response_time_ms = start_time.elapsed().as_millis(), - database.response = val, - request_id = request_id, - otel.trace_id = success_trace_id, - otel.span_id = success_span_id, - "Ping request successful with OpenTelemetry correlation" - ); - Ok(response) - } - Err(e) => { - let (error_trace_id, error_span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::error!( - event = "http_request_error", - http.method = "GET", - http.route = "/ping", - http.status_code = 500, - http.response_time_ms = start_time.elapsed().as_millis(), - request_id = request_id, - otel.trace_id = error_trace_id, - otel.span_id = error_span_id, - error = %e, - "Ping request failed due to database error with OpenTelemetry correlation" - ); - let app_error = AppError::database_with_context(e, "ping request"); - Err(app_error) - } - } - } - .instrument(span) - .await -} - -async fn health_handler( - State(state): State, - Extension(request_id): Extension, -) -> AppResult { - let request_id = request_id.as_str(); - - // Note: OpenTelemetry span creation removed due to dependency version conflicts - - let span = tracing::info_span!( - "health_handler", - http.method = "GET", - http.route = "/health", - request_id = request_id, - ); - - async move { - // Get trace context for structured logging - let (trace_id, span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::info!( - event = "http_request_start", - http.method = "GET", - http.route = "/health", - request_id = request_id, - otel.trace_id = trace_id, - otel.span_id = span_id, - "Processing health check request with OpenTelemetry correlation" - ); - - let start_time = std::time::Instant::now(); - - match state.db.ping().await { - Ok(_) => { - let (success_trace_id, success_span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::info!( - event = "http_request_success", - http.method = "GET", - http.route = "/health", - http.status_code = 200, - http.response_time_ms = start_time.elapsed().as_millis(), - health.status = "healthy", - request_id = request_id, - otel.trace_id = success_trace_id, - otel.span_id = success_span_id, - "Health check passed with OpenTelemetry correlation" - ); - Ok((StatusCode::OK, "ok")) - } - Err(e) => { - let (error_trace_id, error_span_id) = - get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); - - tracing::error!( - event = "http_request_error", - http.method = "GET", - http.route = "/health", - http.status_code = 500, - http.response_time_ms = start_time.elapsed().as_millis(), - health.status = "unhealthy", - request_id = request_id, - otel.trace_id = error_trace_id, - otel.span_id = error_span_id, - error = %e, - "Health check failed due to database error with OpenTelemetry correlation" - ); - let app_error = AppError::database_with_context(e, "health check"); - Err(app_error) - } - } - } - .instrument(span) - .await -} +mod config; +mod controllers; +mod db; +pub mod error; +mod middleware; +mod models; +mod routes; + +use axum::{ + Router, + extract::{Extension, State}, + http::StatusCode, + response::IntoResponse, + routing::{get, post}, +}; +use middleware::request_id_middleware; +use routes::pool_route::pool_routes; +use routes::validator_route::validator_routes; +use std::net::SocketAddr; +use tracing::Instrument; + +use config::db_config::DbConfig; +use config::tracing::{TracingConfig, get_trace_context, init_tracing, shutdown_tracing}; +use db::database::AppState; +use db::database::Database; +use error::{AppError, AppResult}; +use middleware::RequestId; +use routes::market::{create_market_handler, get_market_handler}; + +#[tokio::main] +async fn main() -> Result<(), AppError> { + // Initialize structured logging with OpenTelemetry + let tracing_config = TracingConfig::from_env(); + init_tracing(&tracing_config).map_err(|e| { + eprintln!("Failed to initialize tracing: {e}"); + AppError::Internal(format!("Tracing initialization failed: {e}")) + })?; + + let config = DbConfig::from_env(); + let db = Database::connect(&config).await; + // Run SQLX migrations after connecting to the database + sqlx::migrate!("./migrations") + .run(db.pool()) + .await + .expect("Failed to run migrations"); + + // Check DB connection at startup with structured logging + match db.ping().await { + Ok(_) => { + tracing::info!( + event = "database_connection_success", + database.type = "postgresql", + "Successfully connected to PostgreSQL database" + ); + } + Err(e) => { + tracing::error!( + event = "database_connection_failed", + database.type = "postgresql", + error = %e, + "Failed to connect to database" + ); + shutdown_tracing(); + return Err(AppError::Internal(format!( + "Database connection failed: {e}" + ))); + } + } + + let state = AppState { db }; + + let app = Router::new() + .route("/ping", get(ping_handler)) + .route("/health", get(health_handler)) + .route("/markets", post(create_market_handler)) + .route("/markets/:id", get(get_market_handler)) + .merge(pool_routes()) // Merge the new pool routes + .merge(validator_routes()) + .with_state(state) + .layer(request_id_middleware()); + + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + tracing::info!( + event = "server_starting", + server.address = %addr, + server.port = 3000, + "Starting server with manual OpenTelemetry trace correlation" + ); + + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + + // Set up graceful shutdown + let server = axum::serve(listener, app); + + // Handle shutdown signal + tokio::select! { + result = server => { + if let Err(e) = result { + tracing::error!( + event = "server_error", + error = %e, + "Server error" + ); + } + } + _ = tokio::signal::ctrl_c() => { + tracing::info!( + event = "shutdown_signal_received", + "Received shutdown signal" + ); + } + } + + tracing::info!(event = "server_shutdown", "Server shutdown complete"); + shutdown_tracing(); + Ok(()) +} + +async fn ping_handler( + State(state): State, + Extension(request_id): Extension, +) -> AppResult { + let request_id = request_id.as_str(); + + // Note: OpenTelemetry span creation removed due to dependency version conflicts + + let span = tracing::info_span!( + "ping_handler", + http.method = "GET", + http.route = "/ping", + request_id = request_id, + ); + + async move { + // Get trace context for structured logging + let (trace_id, span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::info!( + event = "http_request_start", + http.method = "GET", + http.route = "/ping", + request_id = request_id, + otel.trace_id = trace_id, + otel.span_id = span_id, + "Processing ping request with OpenTelemetry correlation" + ); + + let start_time = std::time::Instant::now(); + + match state.db.ping().await { + Ok(val) => { + let response = format!("pong: {val}"); + let (success_trace_id, success_span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::info!( + event = "http_request_success", + http.method = "GET", + http.route = "/ping", + http.status_code = 200, + http.response_time_ms = start_time.elapsed().as_millis(), + database.response = val, + request_id = request_id, + otel.trace_id = success_trace_id, + otel.span_id = success_span_id, + "Ping request successful with OpenTelemetry correlation" + ); + Ok(response) + } + Err(e) => { + let (error_trace_id, error_span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::error!( + event = "http_request_error", + http.method = "GET", + http.route = "/ping", + http.status_code = 500, + http.response_time_ms = start_time.elapsed().as_millis(), + request_id = request_id, + otel.trace_id = error_trace_id, + otel.span_id = error_span_id, + error = %e, + "Ping request failed due to database error with OpenTelemetry correlation" + ); + let app_error = AppError::database_with_context(e, "ping request"); + Err(app_error) + } + } + } + .instrument(span) + .await +} + +async fn health_handler( + State(state): State, + Extension(request_id): Extension, +) -> AppResult { + let request_id = request_id.as_str(); + + // Note: OpenTelemetry span creation removed due to dependency version conflicts + + let span = tracing::info_span!( + "health_handler", + http.method = "GET", + http.route = "/health", + request_id = request_id, + ); + + async move { + // Get trace context for structured logging + let (trace_id, span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::info!( + event = "http_request_start", + http.method = "GET", + http.route = "/health", + request_id = request_id, + otel.trace_id = trace_id, + otel.span_id = span_id, + "Processing health check request with OpenTelemetry correlation" + ); + + let start_time = std::time::Instant::now(); + + match state.db.ping().await { + Ok(_) => { + let (success_trace_id, success_span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::info!( + event = "http_request_success", + http.method = "GET", + http.route = "/health", + http.status_code = 200, + http.response_time_ms = start_time.elapsed().as_millis(), + health.status = "healthy", + request_id = request_id, + otel.trace_id = success_trace_id, + otel.span_id = success_span_id, + "Health check passed with OpenTelemetry correlation" + ); + Ok((StatusCode::OK, "ok")) + } + Err(e) => { + let (error_trace_id, error_span_id) = + get_trace_context().unwrap_or(("unknown".to_string(), "unknown".to_string())); + + tracing::error!( + event = "http_request_error", + http.method = "GET", + http.route = "/health", + http.status_code = 500, + http.response_time_ms = start_time.elapsed().as_millis(), + health.status = "unhealthy", + request_id = request_id, + otel.trace_id = error_trace_id, + otel.span_id = error_span_id, + error = %e, + "Health check failed due to database error with OpenTelemetry correlation" + ); + let app_error = AppError::database_with_context(e, "health check"); + Err(app_error) + } + } + } + .instrument(span) + .await +} diff --git a/backend/src/middleware/mod.rs b/backend/src/middleware/mod.rs index 9d760ff..495c5b7 100644 --- a/backend/src/middleware/mod.rs +++ b/backend/src/middleware/mod.rs @@ -1,3 +1,3 @@ -pub mod request_id; - -pub use request_id::{RequestId, request_id_middleware}; +pub mod request_id; + +pub use request_id::{RequestId, request_id_middleware}; diff --git a/backend/src/middleware/request_id.rs b/backend/src/middleware/request_id.rs index 78b6b04..6cd8c28 100644 --- a/backend/src/middleware/request_id.rs +++ b/backend/src/middleware/request_id.rs @@ -1,98 +1,98 @@ -use axum::{extract::Request, http::HeaderValue, response::Response}; -use std::task::{Context, Poll}; -use tower::{Layer, Service}; -use tracing::{Instrument, info_span}; -use uuid::Uuid; - -const REQUEST_ID_HEADER: &str = "x-request-id"; - -#[derive(Clone)] -pub struct RequestIdLayer; - -impl Layer for RequestIdLayer { - type Service = RequestIdService; - - fn layer(&self, service: S) -> Self::Service { - RequestIdService { inner: service } - } -} - -#[derive(Clone)] -pub struct RequestIdService { - inner: S, -} - -impl Service for RequestIdService -where - S: Service + Send + 'static + Clone, - S::Future: Send + 'static, -{ - type Response = S::Response; - type Error = S::Error; - type Future = std::pin::Pin< - Box> + Send>, - >; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, mut request: Request) -> Self::Future { - // Extract or generate request ID - let request_id = extract_or_generate_request_id(&request); - - // Add request ID to request extensions for use in handlers - request - .extensions_mut() - .insert(RequestId(request_id.clone())); - - // Create a span with the request ID for tracing - let span = info_span!("request", request_id = %request_id); - - let mut inner = self.inner.clone(); - - Box::pin( - async move { - let response = inner.call(request).await?; - - // Add request ID to response headers - let mut response = response; - if let Ok(header_value) = HeaderValue::from_str(&request_id) { - response - .headers_mut() - .insert(REQUEST_ID_HEADER, header_value); - } - - Ok(response) - } - .instrument(span), - ) - } -} - -fn extract_or_generate_request_id(request: &Request) -> String { - // Check for existing request ID in headers - if let Some(request_id) = request.headers().get(REQUEST_ID_HEADER) - && let Ok(id) = request_id.to_str() - && !id.is_empty() - { - return id.to_string(); - } - - // Generate new request ID if none provided or invalid - Uuid::new_v4().to_string() -} - -#[derive(Clone)] -pub struct RequestId(pub String); - -impl RequestId { - pub fn as_str(&self) -> &str { - &self.0 - } -} - -// Convenience function to create the middleware -pub fn request_id_middleware() -> RequestIdLayer { - RequestIdLayer -} +use axum::{extract::Request, http::HeaderValue, response::Response}; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; +use tracing::{Instrument, info_span}; +use uuid::Uuid; + +const REQUEST_ID_HEADER: &str = "x-request-id"; + +#[derive(Clone)] +pub struct RequestIdLayer; + +impl Layer for RequestIdLayer { + type Service = RequestIdService; + + fn layer(&self, service: S) -> Self::Service { + RequestIdService { inner: service } + } +} + +#[derive(Clone)] +pub struct RequestIdService { + inner: S, +} + +impl Service for RequestIdService +where + S: Service + Send + 'static + Clone, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = std::pin::Pin< + Box> + Send>, + >; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, mut request: Request) -> Self::Future { + // Extract or generate request ID + let request_id = extract_or_generate_request_id(&request); + + // Add request ID to request extensions for use in handlers + request + .extensions_mut() + .insert(RequestId(request_id.clone())); + + // Create a span with the request ID for tracing + let span = info_span!("request", request_id = %request_id); + + let mut inner = self.inner.clone(); + + Box::pin( + async move { + let response = inner.call(request).await?; + + // Add request ID to response headers + let mut response = response; + if let Ok(header_value) = HeaderValue::from_str(&request_id) { + response + .headers_mut() + .insert(REQUEST_ID_HEADER, header_value); + } + + Ok(response) + } + .instrument(span), + ) + } +} + +fn extract_or_generate_request_id(request: &Request) -> String { + // Check for existing request ID in headers + if let Some(request_id) = request.headers().get(REQUEST_ID_HEADER) + && let Ok(id) = request_id.to_str() + && !id.is_empty() + { + return id.to_string(); + } + + // Generate new request ID if none provided or invalid + Uuid::new_v4().to_string() +} + +#[derive(Clone)] +pub struct RequestId(pub String); + +impl RequestId { + pub fn as_str(&self) -> &str { + &self.0 + } +} + +// Convenience function to create the middleware +pub fn request_id_middleware() -> RequestIdLayer { + RequestIdLayer +} diff --git a/backend/src/models/market.rs b/backend/src/models/market.rs index 2ae2011..9b9ad8a 100644 --- a/backend/src/models/market.rs +++ b/backend/src/models/market.rs @@ -1,71 +1,71 @@ -use bigdecimal::BigDecimal; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct Market { - pub id: i32, - pub name: String, - pub description: Option, - pub category_id: Option, - pub image_url: Option, - pub event_source_url: Option, - pub start_time: Option, - pub lock_time: Option, - pub end_time: Option, - pub option1: Option, - pub option2: Option, - pub min_bet_amount: Option, - pub max_bet_amount: Option, - pub creator_fee: Option, - pub is_private: Option, - pub creator_address: Option, - pub created_timestamp: Option, - pub status: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct NewMarket { - pub name: String, - pub description: Option, - pub category_id: Option, - pub image_url: Option, - pub event_source_url: Option, - pub start_time: Option, - pub lock_time: Option, - pub end_time: Option, - pub option1: Option, - pub option2: Option, - pub min_bet_amount: Option, - pub max_bet_amount: Option, - pub creator_fee: Option, - pub is_private: Option, - pub creator_address: Option, - pub created_timestamp: Option, - pub tags: Option>, -} - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct MarketCategory { - pub id: i32, - pub name: String, -} - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct Tag { - pub id: i32, - pub name: String, - pub created_at: Option>, -} - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct MarketTag { - pub id: i32, - pub market_id: i32, - pub tag_id: i32, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct MarketWithTags { - pub market: Market, - pub tags: Vec, -} +use bigdecimal::BigDecimal; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct Market { + pub id: i32, + pub name: String, + pub description: Option, + pub category_id: Option, + pub image_url: Option, + pub event_source_url: Option, + pub start_time: Option, + pub lock_time: Option, + pub end_time: Option, + pub option1: Option, + pub option2: Option, + pub min_bet_amount: Option, + pub max_bet_amount: Option, + pub creator_fee: Option, + pub is_private: Option, + pub creator_address: Option, + pub created_timestamp: Option, + pub status: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct NewMarket { + pub name: String, + pub description: Option, + pub category_id: Option, + pub image_url: Option, + pub event_source_url: Option, + pub start_time: Option, + pub lock_time: Option, + pub end_time: Option, + pub option1: Option, + pub option2: Option, + pub min_bet_amount: Option, + pub max_bet_amount: Option, + pub creator_fee: Option, + pub is_private: Option, + pub creator_address: Option, + pub created_timestamp: Option, + pub tags: Option>, +} + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct MarketCategory { + pub id: i32, + pub name: String, +} + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct Tag { + pub id: i32, + pub name: String, + pub created_at: Option>, +} + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct MarketTag { + pub id: i32, + pub market_id: i32, + pub tag_id: i32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MarketWithTags { + pub market: Market, + pub tags: Vec, +} diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs index b4bbfdf..c292811 100644 --- a/backend/src/models/mod.rs +++ b/backend/src/models/mod.rs @@ -1,3 +1,3 @@ -pub mod market; -pub mod pool; -pub mod validator; +pub mod market; +pub mod pool; +pub mod validator; diff --git a/backend/src/models/pool.rs b/backend/src/models/pool.rs index fa3d4e1..40e5bd2 100644 --- a/backend/src/models/pool.rs +++ b/backend/src/models/pool.rs @@ -1,62 +1,62 @@ -use bigdecimal::BigDecimal; -use serde::{Deserialize, Serialize}; -use sqlx::Type; - -#[derive(Debug, Serialize, Deserialize)] -pub struct NewPool { - pub market_id: i32, - pub name: String, - pub r#type: i16, - pub description: Option, - pub image_url: Option, - pub event_source_url: Option, - pub start_time: Option, - pub lock_time: Option, - pub end_time: Option, - pub option1: Option, - pub option2: Option, - pub min_bet_amount: Option, - pub max_bet_amount: Option, - pub creator_fee: Option, - pub is_private: Option, - pub category_id: Option, -} - -#[derive(Debug, Serialize, Deserialize, Type, PartialEq, Eq, Clone, Copy)] -#[sqlx(type_name = "pool_status")] -pub enum PoolStatus { - Active, - Locked, - Settled, - Closed, -} - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct Pool { - pub id: i32, - pub market_id: i32, - pub name: String, - pub r#type: i16, - pub description: Option, - pub image_url: Option, - pub event_source_url: Option, - pub start_time: Option, - pub lock_time: Option, - pub end_time: Option, - pub option1: Option, - pub option2: Option, - pub min_bet_amount: Option, - pub max_bet_amount: Option, - pub creator_fee: Option, - pub is_private: Option, - pub category_id: Option, - pub status: PoolStatus, -} - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct UserPool { - pub id: i32, - pub user_id: String, - pub pool_id: i32, - pub amount_staked: BigDecimal, -} +use bigdecimal::BigDecimal; +use serde::{Deserialize, Serialize}; +use sqlx::Type; + +#[derive(Debug, Serialize, Deserialize)] +pub struct NewPool { + pub market_id: i32, + pub name: String, + pub r#type: i16, + pub description: Option, + pub image_url: Option, + pub event_source_url: Option, + pub start_time: Option, + pub lock_time: Option, + pub end_time: Option, + pub option1: Option, + pub option2: Option, + pub min_bet_amount: Option, + pub max_bet_amount: Option, + pub creator_fee: Option, + pub is_private: Option, + pub category_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, Type, PartialEq, Eq, Clone, Copy)] +#[sqlx(type_name = "pool_status")] +pub enum PoolStatus { + Active, + Locked, + Settled, + Closed, +} + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct Pool { + pub id: i32, + pub market_id: i32, + pub name: String, + pub r#type: i16, + pub description: Option, + pub image_url: Option, + pub event_source_url: Option, + pub start_time: Option, + pub lock_time: Option, + pub end_time: Option, + pub option1: Option, + pub option2: Option, + pub min_bet_amount: Option, + pub max_bet_amount: Option, + pub creator_fee: Option, + pub is_private: Option, + pub category_id: Option, + pub status: PoolStatus, +} + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct UserPool { + pub id: i32, + pub user_id: String, + pub pool_id: i32, + pub amount_staked: BigDecimal, +} diff --git a/backend/src/models/validator.rs b/backend/src/models/validator.rs index 3490c76..3de1e66 100644 --- a/backend/src/models/validator.rs +++ b/backend/src/models/validator.rs @@ -1,9 +1,9 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] -pub struct Validator { - pub contract_address: String, - pub is_active: bool, - pub registered_at: chrono::DateTime, - pub updated_at: chrono::DateTime, -} +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] +pub struct Validator { + pub contract_address: String, + pub is_active: bool, + pub registered_at: chrono::DateTime, + pub updated_at: chrono::DateTime, +} diff --git a/backend/src/routes/market.rs b/backend/src/routes/market.rs index cdbb802..354c4d6 100644 --- a/backend/src/routes/market.rs +++ b/backend/src/routes/market.rs @@ -1,131 +1,131 @@ -use crate::AppState; -use crate::{ - controllers::market_controller, - models::market::{MarketWithTags, NewMarket}, -}; -use axum::{ - Json, - extract::{Path, State}, - http::StatusCode, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize)] -pub struct CreateMarketRequest { - pub name: String, - pub description: Option, - pub category_id: Option, - pub image_url: Option, - pub event_source_url: Option, - pub start_time: Option, - pub lock_time: Option, - pub end_time: Option, - pub option1: Option, - pub option2: Option, - pub min_bet_amount: Option, - pub max_bet_amount: Option, - pub creator_fee: Option, - pub is_private: Option, - pub creator_address: Option, - pub tags: Option>, -} - -#[derive(Debug, Serialize)] -pub struct CreateMarketResponse { - pub success: bool, - pub data: MarketWithTags, - pub message: String, -} - -#[derive(Debug, Serialize)] -pub struct GetMarketResponse { - pub success: bool, - pub data: MarketWithTags, - pub message: String, -} - -#[derive(Debug, Serialize)] -pub struct ErrorResponse { - pub success: bool, - pub error: String, - pub message: String, -} - -pub async fn create_market_handler( - State(state): State, - Json(payload): Json, -) -> Result<(StatusCode, Json), (StatusCode, Json)> { - // Convert string amounts to BigDecimal - let min_bet_amount = payload - .min_bet_amount - .as_ref() - .and_then(|s| s.parse::().ok()); - - let max_bet_amount = payload - .max_bet_amount - .as_ref() - .and_then(|s| s.parse::().ok()); - - let new_market = NewMarket { - name: payload.name, - description: payload.description, - category_id: payload.category_id, - image_url: payload.image_url, - event_source_url: payload.event_source_url, - start_time: payload.start_time, - lock_time: payload.lock_time, - end_time: payload.end_time, - option1: payload.option1, - option2: payload.option2, - min_bet_amount, - max_bet_amount, - creator_fee: payload.creator_fee, - is_private: payload.is_private, - creator_address: payload.creator_address, - created_timestamp: Some(chrono::Utc::now().timestamp()), - tags: payload.tags, - }; - - match market_controller::create_market_with_tags(state.db.pool(), &new_market).await { - Ok(market_with_tags) => { - let response = CreateMarketResponse { - success: true, - data: market_with_tags, - message: "Market created successfully".to_string(), - }; - Ok((StatusCode::CREATED, Json(response))) - } - Err(e) => { - let error_response = ErrorResponse { - success: false, - error: format!("Failed to create market: {e}"), - message: "Market creation failed".to_string(), - }; - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))) - } - } -} - -pub async fn get_market_handler( - State(state): State, - Path(market_id): Path, -) -> Result<(StatusCode, Json), (StatusCode, Json)> { - match market_controller::get_market_with_tags(state.db.pool(), market_id).await { - Ok(market_with_tags) => { - let response = GetMarketResponse { - success: true, - data: market_with_tags, - message: "Market retrieved successfully".to_string(), - }; - Ok((StatusCode::OK, Json(response))) - } - Err(e) => { - let error_response = ErrorResponse { - success: false, - error: format!("Failed to retrieve market: {e}"), - message: "Market retrieval failed".to_string(), - }; - Err((StatusCode::NOT_FOUND, Json(error_response))) - } - } -} +use crate::AppState; +use crate::{ + controllers::market_controller, + models::market::{MarketWithTags, NewMarket}, +}; +use axum::{ + Json, + extract::{Path, State}, + http::StatusCode, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +pub struct CreateMarketRequest { + pub name: String, + pub description: Option, + pub category_id: Option, + pub image_url: Option, + pub event_source_url: Option, + pub start_time: Option, + pub lock_time: Option, + pub end_time: Option, + pub option1: Option, + pub option2: Option, + pub min_bet_amount: Option, + pub max_bet_amount: Option, + pub creator_fee: Option, + pub is_private: Option, + pub creator_address: Option, + pub tags: Option>, +} + +#[derive(Debug, Serialize)] +pub struct CreateMarketResponse { + pub success: bool, + pub data: MarketWithTags, + pub message: String, +} + +#[derive(Debug, Serialize)] +pub struct GetMarketResponse { + pub success: bool, + pub data: MarketWithTags, + pub message: String, +} + +#[derive(Debug, Serialize)] +pub struct ErrorResponse { + pub success: bool, + pub error: String, + pub message: String, +} + +pub async fn create_market_handler( + State(state): State, + Json(payload): Json, +) -> Result<(StatusCode, Json), (StatusCode, Json)> { + // Convert string amounts to BigDecimal + let min_bet_amount = payload + .min_bet_amount + .as_ref() + .and_then(|s| s.parse::().ok()); + + let max_bet_amount = payload + .max_bet_amount + .as_ref() + .and_then(|s| s.parse::().ok()); + + let new_market = NewMarket { + name: payload.name, + description: payload.description, + category_id: payload.category_id, + image_url: payload.image_url, + event_source_url: payload.event_source_url, + start_time: payload.start_time, + lock_time: payload.lock_time, + end_time: payload.end_time, + option1: payload.option1, + option2: payload.option2, + min_bet_amount, + max_bet_amount, + creator_fee: payload.creator_fee, + is_private: payload.is_private, + creator_address: payload.creator_address, + created_timestamp: Some(chrono::Utc::now().timestamp()), + tags: payload.tags, + }; + + match market_controller::create_market_with_tags(state.db.pool(), &new_market).await { + Ok(market_with_tags) => { + let response = CreateMarketResponse { + success: true, + data: market_with_tags, + message: "Market created successfully".to_string(), + }; + Ok((StatusCode::CREATED, Json(response))) + } + Err(e) => { + let error_response = ErrorResponse { + success: false, + error: format!("Failed to create market: {e}"), + message: "Market creation failed".to_string(), + }; + Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))) + } + } +} + +pub async fn get_market_handler( + State(state): State, + Path(market_id): Path, +) -> Result<(StatusCode, Json), (StatusCode, Json)> { + match market_controller::get_market_with_tags(state.db.pool(), market_id).await { + Ok(market_with_tags) => { + let response = GetMarketResponse { + success: true, + data: market_with_tags, + message: "Market retrieved successfully".to_string(), + }; + Ok((StatusCode::OK, Json(response))) + } + Err(e) => { + let error_response = ErrorResponse { + success: false, + error: format!("Failed to retrieve market: {e}"), + message: "Market retrieval failed".to_string(), + }; + Err((StatusCode::NOT_FOUND, Json(error_response))) + } + } +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index d55f175..1eb734c 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -1,3 +1,3 @@ -pub mod market; -pub mod pool_route; -pub mod validator_route; +pub mod market; +pub mod pool_route; +pub mod validator_route; diff --git a/backend/src/routes/pool_route.rs b/backend/src/routes/pool_route.rs index c0c3917..c1c7fb5 100644 --- a/backend/src/routes/pool_route.rs +++ b/backend/src/routes/pool_route.rs @@ -1,88 +1,88 @@ -use crate::controllers::pool_controller::*; -use crate::db::database::AppState; -use axum::{ - Router, - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - routing::get, -}; -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct Pagination { - pub limit: Option, - pub offset: Option, -} - -async fn active_pools_handler( - State(state): State, - Query(pagination): Query, -) -> impl IntoResponse { - let limit = pagination.limit.unwrap_or(20); - let offset = pagination.offset.unwrap_or(0); - match get_active_pools(state.db.pool(), limit, offset).await { - Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to fetch active pools", - ) - .into_response(), - } -} - -async fn locked_pools_handler( - State(state): State, - Query(pagination): Query, -) -> impl IntoResponse { - let limit = pagination.limit.unwrap_or(20); - let offset = pagination.offset.unwrap_or(0); - match get_locked_pools(state.db.pool(), limit, offset).await { - Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to fetch locked pools", - ) - .into_response(), - } -} - -async fn settled_pools_handler( - State(state): State, - Query(pagination): Query, -) -> impl IntoResponse { - let limit = pagination.limit.unwrap_or(20); - let offset = pagination.offset.unwrap_or(0); - match get_settled_pools(state.db.pool(), limit, offset).await { - Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to fetch settled pools", - ) - .into_response(), - } -} - -async fn closed_pools_handler( - State(state): State, - Query(pagination): Query, -) -> impl IntoResponse { - let limit = pagination.limit.unwrap_or(20); - let offset = pagination.offset.unwrap_or(0); - match get_closed_pools(state.db.pool(), limit, offset).await { - Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to fetch closed pools", - ) - .into_response(), - } -} - -pub fn pool_routes() -> Router { - Router::new() - .route("/pools/active", get(active_pools_handler)) - .route("/pools/locked", get(locked_pools_handler)) - .route("/pools/settled", get(settled_pools_handler)) - .route("/pools/closed", get(closed_pools_handler)) -} +use crate::controllers::pool_controller::*; +use crate::db::database::AppState; +use axum::{ + Router, + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + routing::get, +}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Pagination { + pub limit: Option, + pub offset: Option, +} + +async fn active_pools_handler( + State(state): State, + Query(pagination): Query, +) -> impl IntoResponse { + let limit = pagination.limit.unwrap_or(20); + let offset = pagination.offset.unwrap_or(0); + match get_active_pools(state.db.pool(), limit, offset).await { + Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch active pools", + ) + .into_response(), + } +} + +async fn locked_pools_handler( + State(state): State, + Query(pagination): Query, +) -> impl IntoResponse { + let limit = pagination.limit.unwrap_or(20); + let offset = pagination.offset.unwrap_or(0); + match get_locked_pools(state.db.pool(), limit, offset).await { + Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch locked pools", + ) + .into_response(), + } +} + +async fn settled_pools_handler( + State(state): State, + Query(pagination): Query, +) -> impl IntoResponse { + let limit = pagination.limit.unwrap_or(20); + let offset = pagination.offset.unwrap_or(0); + match get_settled_pools(state.db.pool(), limit, offset).await { + Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch settled pools", + ) + .into_response(), + } +} + +async fn closed_pools_handler( + State(state): State, + Query(pagination): Query, +) -> impl IntoResponse { + let limit = pagination.limit.unwrap_or(20); + let offset = pagination.offset.unwrap_or(0); + match get_closed_pools(state.db.pool(), limit, offset).await { + Ok(pools) => (StatusCode::OK, axum::Json(pools)).into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch closed pools", + ) + .into_response(), + } +} + +pub fn pool_routes() -> Router { + Router::new() + .route("/pools/active", get(active_pools_handler)) + .route("/pools/locked", get(locked_pools_handler)) + .route("/pools/settled", get(settled_pools_handler)) + .route("/pools/closed", get(closed_pools_handler)) +} diff --git a/backend/src/routes/validator_route.rs b/backend/src/routes/validator_route.rs index ed0792e..f235a38 100644 --- a/backend/src/routes/validator_route.rs +++ b/backend/src/routes/validator_route.rs @@ -1,41 +1,41 @@ -use crate::controllers::validator_controller::*; -use crate::db::database::AppState; -use crate::error::AppResult; -use axum::{ - Json, Router, - extract::{Path, State}, - routing::get, -}; - -pub fn validator_routes() -> Router { - Router::new() - .route("/validator/:address", get(get_validator_handler)) - .route("/validators", get(get_validators_handler)) - .route( - "/validator/:address/status", - get(get_validator_status_handler), - ) -} - -async fn get_validator_handler( - State(state): State, - Path(address): Path, -) -> AppResult> { - let validator = get_validator(state.db.pool(), &address).await?; - Ok(Json(validator)) -} - -async fn get_validators_handler( - State(state): State, -) -> AppResult>> { - let validators = get_validators(state.db.pool()).await?; - Ok(Json(validators)) -} - -async fn get_validator_status_handler( - State(state): State, - Path(address): Path, -) -> AppResult> { - let status = get_validator_status(state.db.pool(), &address).await?; - Ok(Json(status)) -} +use crate::controllers::validator_controller::*; +use crate::db::database::AppState; +use crate::error::AppResult; +use axum::{ + Json, Router, + extract::{Path, State}, + routing::get, +}; + +pub fn validator_routes() -> Router { + Router::new() + .route("/validator/:address", get(get_validator_handler)) + .route("/validators", get(get_validators_handler)) + .route( + "/validator/:address/status", + get(get_validator_status_handler), + ) +} + +async fn get_validator_handler( + State(state): State, + Path(address): Path, +) -> AppResult> { + let validator = get_validator(state.db.pool(), &address).await?; + Ok(Json(validator)) +} + +async fn get_validators_handler( + State(state): State, +) -> AppResult>> { + let validators = get_validators(state.db.pool()).await?; + Ok(Json(validators)) +} + +async fn get_validator_status_handler( + State(state): State, + Path(address): Path, +) -> AppResult> { + let status = get_validator_status(state.db.pool(), &address).await?; + Ok(Json(status)) +} diff --git a/backend/tests/test_health_endpoints.rs b/backend/tests/test_health_endpoints.rs index 4fe6c31..4f571c1 100644 --- a/backend/tests/test_health_endpoints.rs +++ b/backend/tests/test_health_endpoints.rs @@ -1,131 +1,131 @@ -use axum::Router; -use backend::{AppState, db::database::Database}; -use sqlx::PgPool; - -/// Test database configuration -struct TestDb { - pool: PgPool, -} - -impl TestDb { - /// Create a new test database connection - async fn new() -> Self { - // Use environment-based connection for testing - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to test database"); - - // Run migrations - Self::run_migrations(&pool).await; - - Self { pool } - } - - /// Run database migrations - async fn run_migrations(pool: &PgPool) { - sqlx::migrate!("./migrations") - .run(pool) - .await - .expect("Failed to run migrations"); - } -} - -/// Create a test app with the given database pool -fn create_test_app(pool: &PgPool) -> Router { - let db = Database::from_pool(pool.clone()); - let state = AppState { db }; - - Router::new().with_state(state) -} - -#[tokio::test] -async fn test_market_endpoints() { - // Set up test environment using test containers - let test_db = TestDb::new().await; - - // Create test app - let _app = create_test_app(&test_db.pool); - - // Test that we can create a test app - assert!(true, "Test app creation successful"); -} - -#[tokio::test] -async fn test_fixtures() { - // Test that basic assertions work - assert_eq!(2 + 2, 4, "Basic math should work"); - assert!(true, "True should be true"); -} - -#[tokio::test] -async fn test_database_cleanup() { - // Set up test environment using test containers - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Test that we can work with the database - let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; - assert!(result.is_ok(), "Database operations successful"); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_database_connection() { - // Test that we can connect to the test database - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Test a simple query - let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 1); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_migrations() { - // Test that migrations run successfully - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Check that tables exist - let tables = sqlx::query_scalar::<_, String>( - "SELECT string_agg(tablename, ', ') FROM pg_tables WHERE schemaname = 'public'", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - // Verify key tables exist - assert!(tables.contains("market"), "Market table should exist"); - assert!(tables.contains("pool"), "Pool table should exist"); - assert!( - tables.contains("validators"), - "Validators table should exist" - ); - - // Transaction auto-rollbacks, no cleanup needed -} +use axum::Router; +use backend::{AppState, db::database::Database}; +use sqlx::PgPool; + +/// Test database configuration +struct TestDb { + pool: PgPool, +} + +impl TestDb { + /// Create a new test database connection + async fn new() -> Self { + // Use environment-based connection for testing + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to test database"); + + // Run migrations + Self::run_migrations(&pool).await; + + Self { pool } + } + + /// Run database migrations + async fn run_migrations(pool: &PgPool) { + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); + } +} + +/// Create a test app with the given database pool +fn create_test_app(pool: &PgPool) -> Router { + let db = Database::from_pool(pool.clone()); + let state = AppState { db }; + + Router::new().with_state(state) +} + +#[tokio::test] +async fn test_market_endpoints() { + // Set up test environment using test containers + let test_db = TestDb::new().await; + + // Create test app + let _app = create_test_app(&test_db.pool); + + // Test that we can create a test app + assert!(true, "Test app creation successful"); +} + +#[tokio::test] +async fn test_fixtures() { + // Test that basic assertions work + assert_eq!(2 + 2, 4, "Basic math should work"); + assert!(true, "True should be true"); +} + +#[tokio::test] +async fn test_database_cleanup() { + // Set up test environment using test containers + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Test that we can work with the database + let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; + assert!(result.is_ok(), "Database operations successful"); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_database_connection() { + // Test that we can connect to the test database + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Test a simple query + let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_migrations() { + // Test that migrations run successfully + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Check that tables exist + let tables = sqlx::query_scalar::<_, String>( + "SELECT string_agg(tablename, ', ') FROM pg_tables WHERE schemaname = 'public'", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + // Verify key tables exist + assert!(tables.contains("market"), "Market table should exist"); + assert!(tables.contains("pool"), "Pool table should exist"); + assert!( + tables.contains("validators"), + "Validators table should exist" + ); + + // Transaction auto-rollbacks, no cleanup needed +} diff --git a/backend/tests/test_market_api.rs b/backend/tests/test_market_api.rs index 7aba49e..4cbcc70 100644 --- a/backend/tests/test_market_api.rs +++ b/backend/tests/test_market_api.rs @@ -1,207 +1,207 @@ -use axum::Router; -use backend::{AppState, db::database::Database}; -use sqlx::PgPool; - -/// Test database configuration -struct TestDb { - pool: PgPool, -} - -impl TestDb { - /// Create a new test database connection - async fn new() -> Self { - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to test database"); - - // Run migrations - Self::run_migrations(&pool).await; - - Self { pool } - } - - /// Run database migrations - async fn run_migrations(pool: &PgPool) { - sqlx::migrate!("./migrations") - .run(pool) - .await - .expect("Failed to run migrations"); - } -} - -/// Create a test app with basic routes -fn create_test_app(pool: PgPool) -> Router { - let db = Database::from_pool(pool); - let state = AppState { db }; - - Router::new().with_state(state) -} - -#[tokio::test] -async fn test_market_database_setup() { - // Test that we can set up a test database - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Test a simple query - let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 1); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_market_app_creation() { - // Test that we can create a test app - let test_db = TestDb::new().await; - - // Create test app - let _app = create_test_app(test_db.pool.clone()); - - // Test that app was created successfully - assert!(true, "Test app creation successful"); -} - -#[tokio::test] -async fn test_market_data_persistence() { - // Test that we can create and persist market data - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Create a simple market directly in the database - let market_name = "Test Persistence Market"; - let market_description = "Testing data persistence"; - let market_category = "persistence-test"; - - // First create the category - let category_id = - sqlx::query_scalar::<_, i32>("INSERT INTO market_category (name) VALUES ($1) RETURNING id") - .bind(market_category) - .fetch_one(&mut *tx) - .await - .unwrap(); - - // Then create the market - let result = sqlx::query( - "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING id", - ) - .bind(market_name) - .bind(market_description) - .bind(category_id) - .fetch_one(&mut *tx) - .await; - - assert!(result.is_ok(), "Failed to insert market into database"); - - // Verify the market was persisted - let market_id = sqlx::query_scalar::<_, i32>("SELECT id FROM market WHERE name = $1") - .bind(market_name) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!(market_id > 0, "Market ID should be positive"); - - // Verify market data directly from transaction - let db_market_result = sqlx::query_scalar::<_, String>( - "SELECT json_build_object('id', id, 'name', name, 'description', description, 'category_id', category_id)::text FROM market WHERE id = $1::integer" - ) - .bind(market_id) - .fetch_optional(&mut *tx) - .await; - - assert!(db_market_result.is_ok(), "Market query should succeed"); - let db_market_json = db_market_result.unwrap(); - assert!( - db_market_json.is_some(), - "Market should be retrievable from database" - ); - - let market: serde_json::Value = serde_json::from_str(&db_market_json.unwrap()).unwrap(); - assert_eq!(market["name"], market_name); - assert_eq!(market["description"], market_description); - assert_eq!(market["category_id"], category_id); - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Market data persistence test passed"); -} - -#[tokio::test] -async fn test_market_migrations() { - // Test that migrations run successfully - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Check that market table exists - let table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!(table_exists, "Market table should exist after migrations"); - - // Check that market_category table exists - let category_table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market_category')" - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - category_table_exists, - "Market category table should exist after migrations" - ); - - // Check that tags table exists - let tags_table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'tags')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - tags_table_exists, - "Tags table should exist after migrations" - ); - - // Check that market_tags table exists - let market_tags_table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market_tags')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - market_tags_table_exists, - "Market tags table should exist after migrations" - ); - - // Transaction auto-rollbacks, no cleanup needed -} +use axum::Router; +use backend::{AppState, db::database::Database}; +use sqlx::PgPool; + +/// Test database configuration +struct TestDb { + pool: PgPool, +} + +impl TestDb { + /// Create a new test database connection + async fn new() -> Self { + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to test database"); + + // Run migrations + Self::run_migrations(&pool).await; + + Self { pool } + } + + /// Run database migrations + async fn run_migrations(pool: &PgPool) { + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); + } +} + +/// Create a test app with basic routes +fn create_test_app(pool: PgPool) -> Router { + let db = Database::from_pool(pool); + let state = AppState { db }; + + Router::new().with_state(state) +} + +#[tokio::test] +async fn test_market_database_setup() { + // Test that we can set up a test database + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Test a simple query + let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_market_app_creation() { + // Test that we can create a test app + let test_db = TestDb::new().await; + + // Create test app + let _app = create_test_app(test_db.pool.clone()); + + // Test that app was created successfully + assert!(true, "Test app creation successful"); +} + +#[tokio::test] +async fn test_market_data_persistence() { + // Test that we can create and persist market data + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Create a simple market directly in the database + let market_name = "Test Persistence Market"; + let market_description = "Testing data persistence"; + let market_category = "persistence-test"; + + // First create the category + let category_id = + sqlx::query_scalar::<_, i32>("INSERT INTO market_category (name) VALUES ($1) RETURNING id") + .bind(market_category) + .fetch_one(&mut *tx) + .await + .unwrap(); + + // Then create the market + let result = sqlx::query( + "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING id", + ) + .bind(market_name) + .bind(market_description) + .bind(category_id) + .fetch_one(&mut *tx) + .await; + + assert!(result.is_ok(), "Failed to insert market into database"); + + // Verify the market was persisted + let market_id = sqlx::query_scalar::<_, i32>("SELECT id FROM market WHERE name = $1") + .bind(market_name) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!(market_id > 0, "Market ID should be positive"); + + // Verify market data directly from transaction + let db_market_result = sqlx::query_scalar::<_, String>( + "SELECT json_build_object('id', id, 'name', name, 'description', description, 'category_id', category_id)::text FROM market WHERE id = $1::integer" + ) + .bind(market_id) + .fetch_optional(&mut *tx) + .await; + + assert!(db_market_result.is_ok(), "Market query should succeed"); + let db_market_json = db_market_result.unwrap(); + assert!( + db_market_json.is_some(), + "Market should be retrievable from database" + ); + + let market: serde_json::Value = serde_json::from_str(&db_market_json.unwrap()).unwrap(); + assert_eq!(market["name"], market_name); + assert_eq!(market["description"], market_description); + assert_eq!(market["category_id"], category_id); + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Market data persistence test passed"); +} + +#[tokio::test] +async fn test_market_migrations() { + // Test that migrations run successfully + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Check that market table exists + let table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!(table_exists, "Market table should exist after migrations"); + + // Check that market_category table exists + let category_table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market_category')" + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + category_table_exists, + "Market category table should exist after migrations" + ); + + // Check that tags table exists + let tags_table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'tags')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + tags_table_exists, + "Tags table should exist after migrations" + ); + + // Check that market_tags table exists + let market_tags_table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market_tags')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + market_tags_table_exists, + "Market tags table should exist after migrations" + ); + + // Transaction auto-rollbacks, no cleanup needed +} diff --git a/backend/tests/test_market_creation.rs b/backend/tests/test_market_creation.rs index dd7fb7a..5120d02 100644 --- a/backend/tests/test_market_creation.rs +++ b/backend/tests/test_market_creation.rs @@ -1,312 +1,312 @@ -use bigdecimal::BigDecimal; -use futures; -use sqlx::PgPool; -use std::env; -use std::str::FromStr; - -// Test isolation helper -async fn setup_test_environment() -> PgPool { - let database_url = env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to DB"); - - // Run migrations - sqlx::migrate!("./migrations") - .run(&pool) - .await - .expect("Failed to run migrations"); - - // No need to clean up - tests will use transactions - pool -} - -async fn create_test_market( - pool: &PgPool, - market_data: &serde_json::Value, -) -> Result { - use backend::controllers::market_controller; - use backend::models::market::NewMarket; - - let new_market = NewMarket { - name: market_data["name"].as_str().unwrap().to_string(), - description: market_data["description"].as_str().map(|s| s.to_string()), - category_id: market_data["category_id"].as_i64().map(|i| i as i32), - image_url: market_data["image_url"].as_str().map(|s| s.to_string()), - event_source_url: market_data["event_source_url"] - .as_str() - .map(|s| s.to_string()), - start_time: market_data["start_time"].as_i64(), - lock_time: market_data["lock_time"].as_i64(), - end_time: market_data["end_time"].as_i64(), - option1: market_data["option1"].as_str().map(|s| s.to_string()), - option2: market_data["option2"].as_str().map(|s| s.to_string()), - min_bet_amount: market_data["min_bet_amount"] - .as_str() - .and_then(|s| BigDecimal::from_str(s).ok()), - max_bet_amount: market_data["max_bet_amount"] - .as_str() - .and_then(|s| BigDecimal::from_str(s).ok()), - creator_fee: market_data["creator_fee"].as_i64().map(|i| i as i16), - is_private: market_data["is_private"].as_bool(), - creator_address: market_data["creator_address"] - .as_str() - .map(|s| s.to_string()), - created_timestamp: Some(chrono::Utc::now().timestamp()), - tags: market_data["tags"].as_array().map(|arr| { - arr.iter() - .filter_map(|v| v.as_str().map(|s| s.to_string())) - .collect() - }), - }; - - market_controller::create_market_with_tags(pool, &new_market).await -} - -#[tokio::test] -async fn test_market_creation_with_tags() { - let pool = setup_test_environment().await; - - // Start transaction for test isolation - let mut tx = pool.begin().await.expect("Failed to start transaction"); - - // Generate unique timestamp for this test run - let timestamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis(); - - // Test 1: Create market with new tags - let market_data = serde_json::json!({ - "name": format!("creation-test-market-1-{}", timestamp), - "description": "A test market for integration testing", - "category_id": null, - "image_url": "https://example.com/image.jpg", - "event_source_url": "https://example.com/event", - "start_time": 1640995200, // 2022-01-01 00:00:00 UTC - "lock_time": 1640998800, // 2022-01-01 01:00:00 UTC - "end_time": 1641002400, // 2022-01-01 02:00:00 UTC - "option1": "Yes", - "option2": "No", - "min_bet_amount": "0.1", - "max_bet_amount": "100.0", - "creator_fee": 5, - "is_private": false, - "creator_address": "0x1234567890abcdef", - "tags": [ - format!("creation-test-sports-{}", timestamp), - format!("creation-test-football-{}", timestamp), - format!("creation-test-premier-league-{}", timestamp) - ] - }); - - // Simulate the market creation process - let result = create_test_market(&pool, &market_data).await; - assert!(result.is_ok(), "Market creation should succeed"); - - let market_with_tags = result.unwrap(); - assert_eq!( - market_with_tags.market.name, - format!("creation-test-market-1-{}", timestamp) - ); - assert_eq!(market_with_tags.tags.len(), 3); - - // Verify tags were created - let tag_names: Vec = market_with_tags - .tags - .iter() - .map(|t| t.name.clone()) - .collect(); - assert!(tag_names.contains(&format!("creation-test-sports-{}", timestamp))); - assert!(tag_names.contains(&format!("creation-test-football-{}", timestamp))); - assert!(tag_names.contains(&format!("creation-test-premier-league-{}", timestamp))); - - // Test 2: Create another market with some existing tags - let market_data_2 = serde_json::json!({ - "name": format!("creation-test-market-2-{}", timestamp), - "description": "Another test market", - "category_id": null, - "image_url": null, - "event_source_url": null, - "start_time": 1640995200, - "lock_time": 1640998800, - "end_time": 1641002400, - "option1": "Team A", - "option2": "Team B", - "min_bet_amount": "0.5", - "max_bet_amount": "50.0", - "creator_fee": 3, - "is_private": true, - "creator_address": "0xabcdef1234567890", - "tags": [ - format!("creation-test-sports-{}", timestamp), // Reuse existing tag - format!("creation-test-basketball-{}", timestamp), // New tag - format!("creation-test-nba-{}", timestamp) // New tag - ] - }); - - let result_2 = create_test_market(&pool, &market_data_2).await; - assert!(result_2.is_ok(), "Second market creation should succeed"); - - let market_with_tags_2 = result_2.unwrap(); - assert_eq!( - market_with_tags_2.market.name, - format!("creation-test-market-2-{}", timestamp) - ); - assert_eq!(market_with_tags_2.tags.len(), 3); - - // Verify that the "creation-test-sports" tag was reused and new tags were created - let tag_names_2: Vec = market_with_tags_2 - .tags - .iter() - .map(|t| t.name.clone()) - .collect(); - assert!(tag_names_2.contains(&format!("creation-test-sports-{}", timestamp))); - assert!(tag_names_2.contains(&format!("creation-test-basketball-{}", timestamp))); - assert!(tag_names_2.contains(&format!("creation-test-nba-{}", timestamp))); - - // Test 3: Verify that tags table has the correct number of unique tags - let unique_tags_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM tags WHERE name LIKE $1") - .bind(format!("creation-test-%-{}", timestamp)) - .fetch_one(&mut *tx) - .await - .expect("Failed to count tags"); - - // We should have 5 unique tags: creation-test-sports, creation-test-football, creation-test-premier-league, creation-test-basketball, creation-test-nba - assert_eq!( - unique_tags_count.0, 5, - "Expected 5 unique test tags, found {}", - unique_tags_count.0 - ); - - // Test 4: Verify market_tags associations - let market_tags_count: (i64,) = sqlx::query_as( - "SELECT COUNT(*) FROM market_tags mt - JOIN tags t ON mt.tag_id = t.id - WHERE t.name LIKE $1", - ) - .bind(format!("creation-test-%-{}", timestamp)) - .fetch_one(&mut *tx) - .await - .expect("Failed to count market_tags"); - - // We should have 6 associations: 3 for first market + 3 for second market - assert_eq!( - market_tags_count.0, 6, - "Expected 6 unique test market-tag associations, found {}", - market_tags_count.0 - ); - - // Test 5: Test concurrent market creation - let handles: Vec<_> = (0..5) - .map(|i| { - let pool_clone = pool.clone(); - let market_data = serde_json::json!({ - "name": format!("creation-test-concurrent-market-{}-{}", i, timestamp), - "description": format!("Concurrent test market {}", i), - "category_id": null, - "image_url": null, - "event_source_url": null, - "start_time": 1640995200, - "lock_time": 1640998800, - "end_time": 1641002400, - "option1": "Option A", - "option2": "Option B", - "min_bet_amount": "1.0", - "max_bet_amount": "10.0", - "creator_fee": 2, - "is_private": false, - "creator_address": format!("0x{}", i), - "tags": [ - format!("creation-test-concurrent-{}", timestamp), - format!("creation-test-test-{}", timestamp), - format!("creation-test-tag-{}-{}", i, timestamp) - ] - }); - - tokio::spawn(async move { create_test_market(&pool_clone, &market_data).await }) - }) - .collect(); - - let results = futures::future::join_all(handles).await; - for result in results { - assert!(result.is_ok(), "Concurrent market creation should succeed"); - } - - // Verify all concurrent markets were created - let total_markets: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM market WHERE name LIKE $1") - .bind(format!("creation-test-concurrent-market-%-{}", timestamp)) - .fetch_one(&mut *tx) - .await - .expect("Failed to count markets"); - - // 5 concurrent markets - assert_eq!(total_markets.0, 5); - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Market creation with tags test passed"); -} - -#[tokio::test] -async fn test_market_retrieval_with_tags() { - let pool = setup_test_environment().await; - - // Create a test market with tags - let market_data = serde_json::json!({ - "name": "retrieval-test-market", - "description": "Market for testing retrieval", - "category_id": null, - "image_url": "https://example.com/image.jpg", - "event_source_url": "https://example.com/event", - "start_time": 1640995200, - "lock_time": 1640998800, - "end_time": 1641002400, - "option1": "Yes", - "option2": "No", - "min_bet_amount": "0.1", - "max_bet_amount": "100.0", - "creator_fee": 5, - "is_private": false, - "creator_address": "0x1234567890abcdef", - "tags": ["retrieval-test", "retrieval-integration", "retrieval-verification"] - }); - - let created_market = create_test_market(&pool, &market_data).await.unwrap(); - let market_id = created_market.market.id; - - // Test retrieval - let retrieved_market = - backend::controllers::market_controller::get_market_with_tags(&pool, market_id).await; - assert!(retrieved_market.is_ok(), "Market retrieval should succeed"); - - let market_with_tags = retrieved_market.unwrap(); - assert_eq!(market_with_tags.market.id, market_id); - assert_eq!(market_with_tags.market.name, "retrieval-test-market"); - assert_eq!(market_with_tags.tags.len(), 3); - - // Verify all fields are correctly stored and retrieved - assert_eq!( - market_with_tags.market.description, - Some("Market for testing retrieval".to_string()) - ); - assert_eq!( - market_with_tags.market.image_url, - Some("https://example.com/image.jpg".to_string()) - ); - assert_eq!( - market_with_tags.market.event_source_url, - Some("https://example.com/event".to_string()) - ); - assert_eq!(market_with_tags.market.start_time, Some(1640995200)); - assert_eq!(market_with_tags.market.lock_time, Some(1640998800)); - assert_eq!(market_with_tags.market.end_time, Some(1641002400)); - assert_eq!(market_with_tags.market.option1, Some("Yes".to_string())); - assert_eq!(market_with_tags.market.option2, Some("No".to_string())); - assert_eq!(market_with_tags.market.creator_fee, Some(5)); - assert_eq!(market_with_tags.market.is_private, Some(false)); - assert_eq!( - market_with_tags.market.creator_address, - Some("0x1234567890abcdef".to_string()) - ); -} +use bigdecimal::BigDecimal; +use futures; +use sqlx::PgPool; +use std::env; +use std::str::FromStr; + +// Test isolation helper +async fn setup_test_environment() -> PgPool { + let database_url = env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to DB"); + + // Run migrations + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("Failed to run migrations"); + + // No need to clean up - tests will use transactions + pool +} + +async fn create_test_market( + pool: &PgPool, + market_data: &serde_json::Value, +) -> Result { + use backend::controllers::market_controller; + use backend::models::market::NewMarket; + + let new_market = NewMarket { + name: market_data["name"].as_str().unwrap().to_string(), + description: market_data["description"].as_str().map(|s| s.to_string()), + category_id: market_data["category_id"].as_i64().map(|i| i as i32), + image_url: market_data["image_url"].as_str().map(|s| s.to_string()), + event_source_url: market_data["event_source_url"] + .as_str() + .map(|s| s.to_string()), + start_time: market_data["start_time"].as_i64(), + lock_time: market_data["lock_time"].as_i64(), + end_time: market_data["end_time"].as_i64(), + option1: market_data["option1"].as_str().map(|s| s.to_string()), + option2: market_data["option2"].as_str().map(|s| s.to_string()), + min_bet_amount: market_data["min_bet_amount"] + .as_str() + .and_then(|s| BigDecimal::from_str(s).ok()), + max_bet_amount: market_data["max_bet_amount"] + .as_str() + .and_then(|s| BigDecimal::from_str(s).ok()), + creator_fee: market_data["creator_fee"].as_i64().map(|i| i as i16), + is_private: market_data["is_private"].as_bool(), + creator_address: market_data["creator_address"] + .as_str() + .map(|s| s.to_string()), + created_timestamp: Some(chrono::Utc::now().timestamp()), + tags: market_data["tags"].as_array().map(|arr| { + arr.iter() + .filter_map(|v| v.as_str().map(|s| s.to_string())) + .collect() + }), + }; + + market_controller::create_market_with_tags(pool, &new_market).await +} + +#[tokio::test] +async fn test_market_creation_with_tags() { + let pool = setup_test_environment().await; + + // Start transaction for test isolation + let mut tx = pool.begin().await.expect("Failed to start transaction"); + + // Generate unique timestamp for this test run + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(); + + // Test 1: Create market with new tags + let market_data = serde_json::json!({ + "name": format!("creation-test-market-1-{}", timestamp), + "description": "A test market for integration testing", + "category_id": null, + "image_url": "https://example.com/image.jpg", + "event_source_url": "https://example.com/event", + "start_time": 1640995200, // 2022-01-01 00:00:00 UTC + "lock_time": 1640998800, // 2022-01-01 01:00:00 UTC + "end_time": 1641002400, // 2022-01-01 02:00:00 UTC + "option1": "Yes", + "option2": "No", + "min_bet_amount": "0.1", + "max_bet_amount": "100.0", + "creator_fee": 5, + "is_private": false, + "creator_address": "0x1234567890abcdef", + "tags": [ + format!("creation-test-sports-{}", timestamp), + format!("creation-test-football-{}", timestamp), + format!("creation-test-premier-league-{}", timestamp) + ] + }); + + // Simulate the market creation process + let result = create_test_market(&pool, &market_data).await; + assert!(result.is_ok(), "Market creation should succeed"); + + let market_with_tags = result.unwrap(); + assert_eq!( + market_with_tags.market.name, + format!("creation-test-market-1-{}", timestamp) + ); + assert_eq!(market_with_tags.tags.len(), 3); + + // Verify tags were created + let tag_names: Vec = market_with_tags + .tags + .iter() + .map(|t| t.name.clone()) + .collect(); + assert!(tag_names.contains(&format!("creation-test-sports-{}", timestamp))); + assert!(tag_names.contains(&format!("creation-test-football-{}", timestamp))); + assert!(tag_names.contains(&format!("creation-test-premier-league-{}", timestamp))); + + // Test 2: Create another market with some existing tags + let market_data_2 = serde_json::json!({ + "name": format!("creation-test-market-2-{}", timestamp), + "description": "Another test market", + "category_id": null, + "image_url": null, + "event_source_url": null, + "start_time": 1640995200, + "lock_time": 1640998800, + "end_time": 1641002400, + "option1": "Team A", + "option2": "Team B", + "min_bet_amount": "0.5", + "max_bet_amount": "50.0", + "creator_fee": 3, + "is_private": true, + "creator_address": "0xabcdef1234567890", + "tags": [ + format!("creation-test-sports-{}", timestamp), // Reuse existing tag + format!("creation-test-basketball-{}", timestamp), // New tag + format!("creation-test-nba-{}", timestamp) // New tag + ] + }); + + let result_2 = create_test_market(&pool, &market_data_2).await; + assert!(result_2.is_ok(), "Second market creation should succeed"); + + let market_with_tags_2 = result_2.unwrap(); + assert_eq!( + market_with_tags_2.market.name, + format!("creation-test-market-2-{}", timestamp) + ); + assert_eq!(market_with_tags_2.tags.len(), 3); + + // Verify that the "creation-test-sports" tag was reused and new tags were created + let tag_names_2: Vec = market_with_tags_2 + .tags + .iter() + .map(|t| t.name.clone()) + .collect(); + assert!(tag_names_2.contains(&format!("creation-test-sports-{}", timestamp))); + assert!(tag_names_2.contains(&format!("creation-test-basketball-{}", timestamp))); + assert!(tag_names_2.contains(&format!("creation-test-nba-{}", timestamp))); + + // Test 3: Verify that tags table has the correct number of unique tags + let unique_tags_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM tags WHERE name LIKE $1") + .bind(format!("creation-test-%-{}", timestamp)) + .fetch_one(&mut *tx) + .await + .expect("Failed to count tags"); + + // We should have 5 unique tags: creation-test-sports, creation-test-football, creation-test-premier-league, creation-test-basketball, creation-test-nba + assert_eq!( + unique_tags_count.0, 5, + "Expected 5 unique test tags, found {}", + unique_tags_count.0 + ); + + // Test 4: Verify market_tags associations + let market_tags_count: (i64,) = sqlx::query_as( + "SELECT COUNT(*) FROM market_tags mt + JOIN tags t ON mt.tag_id = t.id + WHERE t.name LIKE $1", + ) + .bind(format!("creation-test-%-{}", timestamp)) + .fetch_one(&mut *tx) + .await + .expect("Failed to count market_tags"); + + // We should have 6 associations: 3 for first market + 3 for second market + assert_eq!( + market_tags_count.0, 6, + "Expected 6 unique test market-tag associations, found {}", + market_tags_count.0 + ); + + // Test 5: Test concurrent market creation + let handles: Vec<_> = (0..5) + .map(|i| { + let pool_clone = pool.clone(); + let market_data = serde_json::json!({ + "name": format!("creation-test-concurrent-market-{}-{}", i, timestamp), + "description": format!("Concurrent test market {}", i), + "category_id": null, + "image_url": null, + "event_source_url": null, + "start_time": 1640995200, + "lock_time": 1640998800, + "end_time": 1641002400, + "option1": "Option A", + "option2": "Option B", + "min_bet_amount": "1.0", + "max_bet_amount": "10.0", + "creator_fee": 2, + "is_private": false, + "creator_address": format!("0x{}", i), + "tags": [ + format!("creation-test-concurrent-{}", timestamp), + format!("creation-test-test-{}", timestamp), + format!("creation-test-tag-{}-{}", i, timestamp) + ] + }); + + tokio::spawn(async move { create_test_market(&pool_clone, &market_data).await }) + }) + .collect(); + + let results = futures::future::join_all(handles).await; + for result in results { + assert!(result.is_ok(), "Concurrent market creation should succeed"); + } + + // Verify all concurrent markets were created + let total_markets: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM market WHERE name LIKE $1") + .bind(format!("creation-test-concurrent-market-%-{}", timestamp)) + .fetch_one(&mut *tx) + .await + .expect("Failed to count markets"); + + // 5 concurrent markets + assert_eq!(total_markets.0, 5); + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Market creation with tags test passed"); +} + +#[tokio::test] +async fn test_market_retrieval_with_tags() { + let pool = setup_test_environment().await; + + // Create a test market with tags + let market_data = serde_json::json!({ + "name": "retrieval-test-market", + "description": "Market for testing retrieval", + "category_id": null, + "image_url": "https://example.com/image.jpg", + "event_source_url": "https://example.com/event", + "start_time": 1640995200, + "lock_time": 1640998800, + "end_time": 1641002400, + "option1": "Yes", + "option2": "No", + "min_bet_amount": "0.1", + "max_bet_amount": "100.0", + "creator_fee": 5, + "is_private": false, + "creator_address": "0x1234567890abcdef", + "tags": ["retrieval-test", "retrieval-integration", "retrieval-verification"] + }); + + let created_market = create_test_market(&pool, &market_data).await.unwrap(); + let market_id = created_market.market.id; + + // Test retrieval + let retrieved_market = + backend::controllers::market_controller::get_market_with_tags(&pool, market_id).await; + assert!(retrieved_market.is_ok(), "Market retrieval should succeed"); + + let market_with_tags = retrieved_market.unwrap(); + assert_eq!(market_with_tags.market.id, market_id); + assert_eq!(market_with_tags.market.name, "retrieval-test-market"); + assert_eq!(market_with_tags.tags.len(), 3); + + // Verify all fields are correctly stored and retrieved + assert_eq!( + market_with_tags.market.description, + Some("Market for testing retrieval".to_string()) + ); + assert_eq!( + market_with_tags.market.image_url, + Some("https://example.com/image.jpg".to_string()) + ); + assert_eq!( + market_with_tags.market.event_source_url, + Some("https://example.com/event".to_string()) + ); + assert_eq!(market_with_tags.market.start_time, Some(1640995200)); + assert_eq!(market_with_tags.market.lock_time, Some(1640998800)); + assert_eq!(market_with_tags.market.end_time, Some(1641002400)); + assert_eq!(market_with_tags.market.option1, Some("Yes".to_string())); + assert_eq!(market_with_tags.market.option2, Some("No".to_string())); + assert_eq!(market_with_tags.market.creator_fee, Some(5)); + assert_eq!(market_with_tags.market.is_private, Some(false)); + assert_eq!( + market_with_tags.market.creator_address, + Some("0x1234567890abcdef".to_string()) + ); +} diff --git a/backend/tests/test_pool.rs b/backend/tests/test_pool.rs index 338d83f..7c50ec6 100644 --- a/backend/tests/test_pool.rs +++ b/backend/tests/test_pool.rs @@ -1,242 +1,242 @@ -use axum::{ - Router, - body::Body, - http::{Request, StatusCode}, -}; -use backend::controllers::pool_controller::*; -use backend::models::pool::{NewPool, Pool, PoolStatus}; -use backend::routes::pool_route::pool_routes; -use bigdecimal::BigDecimal; -use sqlx::{PgPool, Row}; -use std::env; -use tower::ServiceExt; // for .oneshot() - -// Define our test database structure -struct TestDb { - pool: PgPool, -} - -impl TestDb { - async fn new() -> Self { - let database_url = env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to DB"); - - // Run migrations - sqlx::migrate!("./migrations") - .run(&pool) - .await - .expect("Failed to run migrations"); - - Self { pool } - } -} - -#[tokio::test] -async fn test_pool_controller_functions() { - dotenvy::dotenv().ok(); - let test_db = TestDb::new().await; - - // Insert a market row - let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") - .bind("Test Market") - .fetch_one(&test_db.pool) - .await - .expect("Insert market failed"); - let market_id: i32 = market_row.get("id"); - - // Test create_pool - let new_pool = NewPool { - market_id, - name: "Test Pool".to_string(), - r#type: 1, - description: Some("desc".to_string()), - image_url: None, - event_source_url: None, - start_time: None, - lock_time: None, - end_time: None, - option1: Some("A".to_string()), - option2: Some("B".to_string()), - min_bet_amount: Some(BigDecimal::from(1)), - max_bet_amount: Some(BigDecimal::from(100)), - creator_fee: Some(5), - is_private: Some(false), - category_id: None, - }; - let pool_obj = create_pool(&test_db.pool, &new_pool) - .await - .expect("create_pool failed"); - assert_eq!(pool_obj.name, "Test Pool"); - - // Test get_pool - let fetched = get_pool(&test_db.pool, pool_obj.id) - .await - .expect("get_pool failed"); - assert_eq!(fetched.id, pool_obj.id); - - // Insert pools with different statuses - let statuses = [ - PoolStatus::Active, - PoolStatus::Locked, - PoolStatus::Settled, - PoolStatus::Closed, - ]; - let mut pool_ids = vec![pool_obj.id]; - for status in statuses.iter() { - let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") - .bind(market_id) - .bind(format!("Pool-{status:?}")) - .bind(1) - .bind(format!("{status:?}")) - .fetch_one(&test_db.pool) - .await - .expect("Insert pool failed"); - pool_ids.push(row.get::("id")); - } - - // Test get_pools_by_status - let active_pools = get_pools_by_status(&test_db.pool, "Active", 10, 0) - .await - .expect("get_pools_by_status failed"); - assert!(active_pools.iter().any(|p| p.status == PoolStatus::Active)); - - // Test get_active_pools - let actives = get_active_pools(&test_db.pool, 10, 0) - .await - .expect("get_active_pools failed"); - assert!(actives.iter().all(|p| p.status == PoolStatus::Active)); - - // Test get_locked_pools - let locked = get_locked_pools(&test_db.pool, 10, 0) - .await - .expect("get_locked_pools failed"); - assert!(locked.iter().all(|p| p.status == PoolStatus::Locked)); - - // Test get_settled_pools - let settled = get_settled_pools(&test_db.pool, 10, 0) - .await - .expect("get_settled_pools failed"); - assert!(settled.iter().all(|p| p.status == PoolStatus::Settled)); - - // Test get_closed_pools - let closed = get_closed_pools(&test_db.pool, 10, 0) - .await - .expect("get_closed_pools failed"); - assert!(closed.iter().all(|p| p.status == PoolStatus::Closed)); - - // Test create_user_pool - let user_pool = create_user_pool(&test_db.pool, "user1", pool_obj.id, &BigDecimal::from(10)) - .await - .expect("create_user_pool failed"); - assert_eq!(user_pool.user_id, "user1"); - - // Test get_user_pool - let fetched_user_pool = get_user_pool(&test_db.pool, user_pool.id) - .await - .expect("get_user_pool failed"); - assert_eq!(fetched_user_pool.id, user_pool.id); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_pools_routes() { - dotenvy::dotenv().ok(); - let test_db = TestDb::new().await; - - // Insert a market row - let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") - .bind("RouteTest Market") - .fetch_one(&test_db.pool) - .await - .expect("Insert market failed"); - let market_id: i32 = market_row.get("id"); - - // Insert pools with different statuses - let statuses = ["Active", "Locked", "Settled", "Closed"]; - let mut pool_ids = vec![]; - for status in statuses.iter() { - let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") - .bind(market_id) - .bind(format!("Pool-{status}")) - .bind(1) - .bind(*status) - .fetch_one(&test_db.pool) - .await - .expect("Insert pool failed"); - pool_ids.push(row.get::("id")); - } - - // Build app state and router - let state = backend::db::database::AppState { - db: backend::db::database::Database { - pool: test_db.pool.clone(), - }, - }; - let app = pool_routes().with_state(state); - - // Helper to test each route - async fn test_route(app: &Router, route: &str, expected_status: &str) { - let response = app - .clone() - .oneshot(Request::builder().uri(route).body(Body::empty()).unwrap()) - .await - .unwrap(); - assert_eq!(response.status(), StatusCode::OK); - let body = axum::body::to_bytes(response.into_body(), usize::MAX) - .await - .unwrap(); - let body_str = std::str::from_utf8(&body).unwrap(); - assert!( - body_str.contains(expected_status), - "Response for {route} should contain status {expected_status}" - ); - } - - test_route(&app, "/pools/active", "Active").await; - test_route(&app, "/pools/locked", "Locked").await; - test_route(&app, "/pools/settled", "Settled").await; - test_route(&app, "/pools/closed", "Closed").await; - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_pool_status_enum_mapping() { - dotenvy::dotenv().ok(); - let test_db = TestDb::new().await; - - // Insert a market row to satisfy foreign key constraint - let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") - .bind("Test Market") - .fetch_one(&test_db.pool) - .await - .expect("Insert market failed"); - let market_id: i32 = market_row.get("id"); - - // Insert a pool with status 'Locked' (as &str, cast to pool_status) - let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") - .bind(market_id) - .bind("Test Pool") - .bind(1) - .bind("Locked") - .fetch_one(&test_db.pool) - .await - .expect("Insert failed"); - - let id: i32 = row.get("id"); - - // Query the pool and check status mapping - let pool_obj: Pool = sqlx::query_as::<_, Pool>("SELECT * FROM pool WHERE id = $1") - .bind(id) - .fetch_one(&test_db.pool) - .await - .expect("Fetch failed"); - - assert_eq!(pool_obj.status, PoolStatus::Locked); - - // Transaction auto-rollbacks, no cleanup needed -} +use axum::{ + Router, + body::Body, + http::{Request, StatusCode}, +}; +use backend::controllers::pool_controller::*; +use backend::models::pool::{NewPool, Pool, PoolStatus}; +use backend::routes::pool_route::pool_routes; +use bigdecimal::BigDecimal; +use sqlx::{PgPool, Row}; +use std::env; +use tower::ServiceExt; // for .oneshot() + +// Define our test database structure +struct TestDb { + pool: PgPool, +} + +impl TestDb { + async fn new() -> Self { + let database_url = env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to DB"); + + // Run migrations + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("Failed to run migrations"); + + Self { pool } + } +} + +#[tokio::test] +async fn test_pool_controller_functions() { + dotenvy::dotenv().ok(); + let test_db = TestDb::new().await; + + // Insert a market row + let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") + .bind("Test Market") + .fetch_one(&test_db.pool) + .await + .expect("Insert market failed"); + let market_id: i32 = market_row.get("id"); + + // Test create_pool + let new_pool = NewPool { + market_id, + name: "Test Pool".to_string(), + r#type: 1, + description: Some("desc".to_string()), + image_url: None, + event_source_url: None, + start_time: None, + lock_time: None, + end_time: None, + option1: Some("A".to_string()), + option2: Some("B".to_string()), + min_bet_amount: Some(BigDecimal::from(1)), + max_bet_amount: Some(BigDecimal::from(100)), + creator_fee: Some(5), + is_private: Some(false), + category_id: None, + }; + let pool_obj = create_pool(&test_db.pool, &new_pool) + .await + .expect("create_pool failed"); + assert_eq!(pool_obj.name, "Test Pool"); + + // Test get_pool + let fetched = get_pool(&test_db.pool, pool_obj.id) + .await + .expect("get_pool failed"); + assert_eq!(fetched.id, pool_obj.id); + + // Insert pools with different statuses + let statuses = [ + PoolStatus::Active, + PoolStatus::Locked, + PoolStatus::Settled, + PoolStatus::Closed, + ]; + let mut pool_ids = vec![pool_obj.id]; + for status in statuses.iter() { + let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") + .bind(market_id) + .bind(format!("Pool-{status:?}")) + .bind(1) + .bind(format!("{status:?}")) + .fetch_one(&test_db.pool) + .await + .expect("Insert pool failed"); + pool_ids.push(row.get::("id")); + } + + // Test get_pools_by_status + let active_pools = get_pools_by_status(&test_db.pool, "Active", 10, 0) + .await + .expect("get_pools_by_status failed"); + assert!(active_pools.iter().any(|p| p.status == PoolStatus::Active)); + + // Test get_active_pools + let actives = get_active_pools(&test_db.pool, 10, 0) + .await + .expect("get_active_pools failed"); + assert!(actives.iter().all(|p| p.status == PoolStatus::Active)); + + // Test get_locked_pools + let locked = get_locked_pools(&test_db.pool, 10, 0) + .await + .expect("get_locked_pools failed"); + assert!(locked.iter().all(|p| p.status == PoolStatus::Locked)); + + // Test get_settled_pools + let settled = get_settled_pools(&test_db.pool, 10, 0) + .await + .expect("get_settled_pools failed"); + assert!(settled.iter().all(|p| p.status == PoolStatus::Settled)); + + // Test get_closed_pools + let closed = get_closed_pools(&test_db.pool, 10, 0) + .await + .expect("get_closed_pools failed"); + assert!(closed.iter().all(|p| p.status == PoolStatus::Closed)); + + // Test create_user_pool + let user_pool = create_user_pool(&test_db.pool, "user1", pool_obj.id, &BigDecimal::from(10)) + .await + .expect("create_user_pool failed"); + assert_eq!(user_pool.user_id, "user1"); + + // Test get_user_pool + let fetched_user_pool = get_user_pool(&test_db.pool, user_pool.id) + .await + .expect("get_user_pool failed"); + assert_eq!(fetched_user_pool.id, user_pool.id); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_pools_routes() { + dotenvy::dotenv().ok(); + let test_db = TestDb::new().await; + + // Insert a market row + let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") + .bind("RouteTest Market") + .fetch_one(&test_db.pool) + .await + .expect("Insert market failed"); + let market_id: i32 = market_row.get("id"); + + // Insert pools with different statuses + let statuses = ["Active", "Locked", "Settled", "Closed"]; + let mut pool_ids = vec![]; + for status in statuses.iter() { + let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") + .bind(market_id) + .bind(format!("Pool-{status}")) + .bind(1) + .bind(*status) + .fetch_one(&test_db.pool) + .await + .expect("Insert pool failed"); + pool_ids.push(row.get::("id")); + } + + // Build app state and router + let state = backend::db::database::AppState { + db: backend::db::database::Database { + pool: test_db.pool.clone(), + }, + }; + let app = pool_routes().with_state(state); + + // Helper to test each route + async fn test_route(app: &Router, route: &str, expected_status: &str) { + let response = app + .clone() + .oneshot(Request::builder().uri(route).body(Body::empty()).unwrap()) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let body_str = std::str::from_utf8(&body).unwrap(); + assert!( + body_str.contains(expected_status), + "Response for {route} should contain status {expected_status}" + ); + } + + test_route(&app, "/pools/active", "Active").await; + test_route(&app, "/pools/locked", "Locked").await; + test_route(&app, "/pools/settled", "Settled").await; + test_route(&app, "/pools/closed", "Closed").await; + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_pool_status_enum_mapping() { + dotenvy::dotenv().ok(); + let test_db = TestDb::new().await; + + // Insert a market row to satisfy foreign key constraint + let market_row = sqlx::query("INSERT INTO market (name) VALUES ($1) RETURNING id") + .bind("Test Market") + .fetch_one(&test_db.pool) + .await + .expect("Insert market failed"); + let market_id: i32 = market_row.get("id"); + + // Insert a pool with status 'Locked' (as &str, cast to pool_status) + let row = sqlx::query("INSERT INTO pool (market_id, name, type, status) VALUES ($1, $2, $3, $4::pool_status) RETURNING id") + .bind(market_id) + .bind("Test Pool") + .bind(1) + .bind("Locked") + .fetch_one(&test_db.pool) + .await + .expect("Insert failed"); + + let id: i32 = row.get("id"); + + // Query the pool and check status mapping + let pool_obj: Pool = sqlx::query_as::<_, Pool>("SELECT * FROM pool WHERE id = $1") + .bind(id) + .fetch_one(&test_db.pool) + .await + .expect("Fetch failed"); + + assert_eq!(pool_obj.status, PoolStatus::Locked); + + // Transaction auto-rollbacks, no cleanup needed +} diff --git a/backend/tests/test_pool_api.rs b/backend/tests/test_pool_api.rs index 9d93168..4cbbc27 100644 --- a/backend/tests/test_pool_api.rs +++ b/backend/tests/test_pool_api.rs @@ -1,232 +1,232 @@ -use axum::Router; -use backend::{AppState, db::database::Database}; -use sqlx::PgPool; - -/// Test database configuration -struct TestDb { - pool: PgPool, -} - -impl TestDb { - /// Create a new test database connection - async fn new() -> Self { - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to test database"); - - // Run migrations - Self::run_migrations(&pool).await; - - Self { pool } - } - - /// Run database migrations - async fn run_migrations(pool: &PgPool) { - sqlx::migrate!("./migrations") - .run(pool) - .await - .expect("Failed to run migrations"); - } - - /// Create a test market for pool testing - async fn create_test_market(&self, name: &str, category: &str) -> String { - // First create the category - let category_id = sqlx::query_scalar::<_, i32>( - "INSERT INTO market_category (name) VALUES ($1) RETURNING id", - ) - .bind(category) - .fetch_one(&self.pool) - .await - .unwrap(); - - // Then create the market - let result = sqlx::query_scalar::<_, i32>( - "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING id", - ) - .bind(name) - .bind(format!("Test market for {}", name)) - .bind(category_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - result.to_string() - } -} - -/// Create a test app with basic routes -fn create_test_app(pool: PgPool) -> Router { - let db = Database::from_pool(pool); - let state = AppState { db }; - - Router::new().with_state(state) -} - -#[tokio::test] -async fn test_pool_database_setup() { - // Test that we can set up a test database - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Test a simple query - let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 1); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_pool_app_creation() { - // Test that we can create a test app - let test_db = TestDb::new().await; - - // Create test app - let _app = create_test_app(test_db.pool.clone()); - - // Test that app was created successfully - assert!(true, "Test app creation successful"); -} - -#[tokio::test] -async fn test_pool_data_persistence() { - // Test that we can create and persist pool data - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // First create a test market - let market_id = test_db - .create_test_market("Pool Test Market", "pool-test") - .await; - - // Create a pool associated with the market - let pool_name = format!( - "Test Persistence Pool {}", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis() - ); - let pool_description = "Testing pool data persistence"; - let pool_status = "Active"; - - let parsed_market_id = market_id.parse::().unwrap(); - - let result = sqlx::query( - "INSERT INTO pool (name, description, market_id, status, type) VALUES ($1, $2, $3, $4::pool_status, $5) RETURNING id" - ) - .bind(&pool_name) - .bind(pool_description) - .bind(parsed_market_id) - .bind(pool_status) - .bind(1i16) // pool type - .fetch_one(&mut *tx) - .await; - - // Clone pool_name for later use - let pool_name_clone = pool_name.clone(); - - if let Err(ref e) = result { - println!("Pool insertion failed: {}", e); - } - assert!(result.is_ok(), "Failed to insert pool into database"); - - // Verify the pool was persisted - let pool_id = sqlx::query_scalar::<_, i32>("SELECT id FROM pool WHERE name = $1") - .bind(&pool_name_clone) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!(pool_id > 0, "Pool ID should be positive"); - - // Verify pool data directly from transaction - let db_pool_result = sqlx::query_scalar::<_, String>( - "SELECT json_build_object('id', id, 'name', name, 'description', description, 'market_id', market_id, 'status', status)::text FROM pool WHERE id = $1::integer" - ) - .bind(pool_id) - .fetch_optional(&mut *tx) - .await; - - assert!(db_pool_result.is_ok(), "Pool query should succeed"); - let db_pool_json = db_pool_result.unwrap(); - assert!( - db_pool_json.is_some(), - "Pool should be retrievable from database" - ); - - let pool: serde_json::Value = serde_json::from_str(&db_pool_json.unwrap()).unwrap(); - assert_eq!(pool["name"], pool_name); - assert_eq!(pool["description"], pool_description); - assert_eq!(pool["market_id"], market_id.parse::().unwrap()); - assert_eq!(pool["status"], pool_status); - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Pool data persistence test passed"); -} - -#[tokio::test] -async fn test_pool_migrations() { - // Test that migrations run successfully - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Check that pool table exists - let table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'pool')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!(table_exists, "Pool table should exist after migrations"); - - // Check that user_pool table exists - let user_pool_table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'user_pool')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - user_pool_table_exists, - "User pool table should exist after migrations" - ); - - // Check that market table exists (required for pool foreign key) - let market_table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - market_table_exists, - "Market table should exist after migrations" - ); - - // Transaction auto-rollbacks, no cleanup needed -} +use axum::Router; +use backend::{AppState, db::database::Database}; +use sqlx::PgPool; + +/// Test database configuration +struct TestDb { + pool: PgPool, +} + +impl TestDb { + /// Create a new test database connection + async fn new() -> Self { + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to test database"); + + // Run migrations + Self::run_migrations(&pool).await; + + Self { pool } + } + + /// Run database migrations + async fn run_migrations(pool: &PgPool) { + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); + } + + /// Create a test market for pool testing + async fn create_test_market(&self, name: &str, category: &str) -> String { + // First create the category + let category_id = sqlx::query_scalar::<_, i32>( + "INSERT INTO market_category (name) VALUES ($1) RETURNING id", + ) + .bind(category) + .fetch_one(&self.pool) + .await + .unwrap(); + + // Then create the market + let result = sqlx::query_scalar::<_, i32>( + "INSERT INTO market (name, description, category_id) VALUES ($1, $2, $3) RETURNING id", + ) + .bind(name) + .bind(format!("Test market for {}", name)) + .bind(category_id) + .fetch_one(&self.pool) + .await + .unwrap(); + + result.to_string() + } +} + +/// Create a test app with basic routes +fn create_test_app(pool: PgPool) -> Router { + let db = Database::from_pool(pool); + let state = AppState { db }; + + Router::new().with_state(state) +} + +#[tokio::test] +async fn test_pool_database_setup() { + // Test that we can set up a test database + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Test a simple query + let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_pool_app_creation() { + // Test that we can create a test app + let test_db = TestDb::new().await; + + // Create test app + let _app = create_test_app(test_db.pool.clone()); + + // Test that app was created successfully + assert!(true, "Test app creation successful"); +} + +#[tokio::test] +async fn test_pool_data_persistence() { + // Test that we can create and persist pool data + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // First create a test market + let market_id = test_db + .create_test_market("Pool Test Market", "pool-test") + .await; + + // Create a pool associated with the market + let pool_name = format!( + "Test Persistence Pool {}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + ); + let pool_description = "Testing pool data persistence"; + let pool_status = "Active"; + + let parsed_market_id = market_id.parse::().unwrap(); + + let result = sqlx::query( + "INSERT INTO pool (name, description, market_id, status, type) VALUES ($1, $2, $3, $4::pool_status, $5) RETURNING id" + ) + .bind(&pool_name) + .bind(pool_description) + .bind(parsed_market_id) + .bind(pool_status) + .bind(1i16) // pool type + .fetch_one(&mut *tx) + .await; + + // Clone pool_name for later use + let pool_name_clone = pool_name.clone(); + + if let Err(ref e) = result { + println!("Pool insertion failed: {}", e); + } + assert!(result.is_ok(), "Failed to insert pool into database"); + + // Verify the pool was persisted + let pool_id = sqlx::query_scalar::<_, i32>("SELECT id FROM pool WHERE name = $1") + .bind(&pool_name_clone) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!(pool_id > 0, "Pool ID should be positive"); + + // Verify pool data directly from transaction + let db_pool_result = sqlx::query_scalar::<_, String>( + "SELECT json_build_object('id', id, 'name', name, 'description', description, 'market_id', market_id, 'status', status)::text FROM pool WHERE id = $1::integer" + ) + .bind(pool_id) + .fetch_optional(&mut *tx) + .await; + + assert!(db_pool_result.is_ok(), "Pool query should succeed"); + let db_pool_json = db_pool_result.unwrap(); + assert!( + db_pool_json.is_some(), + "Pool should be retrievable from database" + ); + + let pool: serde_json::Value = serde_json::from_str(&db_pool_json.unwrap()).unwrap(); + assert_eq!(pool["name"], pool_name); + assert_eq!(pool["description"], pool_description); + assert_eq!(pool["market_id"], market_id.parse::().unwrap()); + assert_eq!(pool["status"], pool_status); + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Pool data persistence test passed"); +} + +#[tokio::test] +async fn test_pool_migrations() { + // Test that migrations run successfully + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Check that pool table exists + let table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'pool')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!(table_exists, "Pool table should exist after migrations"); + + // Check that user_pool table exists + let user_pool_table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'user_pool')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + user_pool_table_exists, + "User pool table should exist after migrations" + ); + + // Check that market table exists (required for pool foreign key) + let market_table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'market')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + market_table_exists, + "Market table should exist after migrations" + ); + + // Transaction auto-rollbacks, no cleanup needed +} diff --git a/backend/tests/test_request_id.rs b/backend/tests/test_request_id.rs index 54f501c..30c6dba 100644 --- a/backend/tests/test_request_id.rs +++ b/backend/tests/test_request_id.rs @@ -1,100 +1,100 @@ -use axum::{Router, body::Body, http::Request, routing::get}; -use tower::ServiceExt; -use uuid::Uuid; - -use backend::middleware::request_id_middleware; - -async fn test_handler() -> &'static str { - "Hello, World!" -} - -#[tokio::test] -async fn test_request_id_middleware() { - // Create a simple app with our middleware - let app = Router::new() - .route("/test", get(test_handler)) - .layer(request_id_middleware()); - - // Test 1: Request without X-Request-ID header (should generate one) - let request = Request::builder().uri("/test").body(Body::empty()).unwrap(); - - let response = app.clone().oneshot(request).await.unwrap(); - - // Check that response has X-Request-ID header - let request_id = response.headers().get("X-Request-ID"); - assert!( - request_id.is_some(), - "Response should have X-Request-ID header" - ); - - let request_id_value = request_id.unwrap().to_str().unwrap(); - assert!( - !request_id_value.is_empty(), - "Request ID should not be empty" - ); - - // Verify it's a valid UUID - assert!( - Uuid::parse_str(request_id_value).is_ok(), - "Request ID should be a valid UUID" - ); - - // Test 2: Request with existing X-Request-ID header (should preserve it) - let existing_request_id = "test-request-id-12345"; - let request = Request::builder() - .uri("/test") - .header("X-Request-ID", existing_request_id) - .body(Body::empty()) - .unwrap(); - - let response = app.oneshot(request).await.unwrap(); - - // Check that response has the same X-Request-ID header - let request_id = response.headers().get("X-Request-ID"); - assert!( - request_id.is_some(), - "Response should have X-Request-ID header" - ); - - let request_id_value = request_id.unwrap().to_str().unwrap(); - assert_eq!( - request_id_value, existing_request_id, - "Request ID should be preserved" - ); -} - -#[tokio::test] -async fn test_request_id_middleware_with_empty_header() { - // Create a simple app with our middleware - let app = Router::new() - .route("/test", get(test_handler)) - .layer(request_id_middleware()); - - // Test: Request with empty X-Request-ID header (should generate new one) - let request = Request::builder() - .uri("/test") - .header("X-Request-ID", "") - .body(Body::empty()) - .unwrap(); - - let response = app.oneshot(request).await.unwrap(); - - // Check that response has X-Request-ID header - let request_id = response.headers().get("X-Request-ID"); - assert!( - request_id.is_some(), - "Response should have X-Request-ID header" - ); - - let request_id_value = request_id.unwrap().to_str().unwrap(); - assert!( - !request_id_value.is_empty(), - "Request ID should not be empty" - ); - - // Verify it's a valid UUID (should generate new one since original was empty) - assert!( - Uuid::parse_str(request_id_value).is_ok(), - "Request ID should be a valid UUID" - ); -} +use axum::{Router, body::Body, http::Request, routing::get}; +use tower::ServiceExt; +use uuid::Uuid; + +use backend::middleware::request_id_middleware; + +async fn test_handler() -> &'static str { + "Hello, World!" +} + +#[tokio::test] +async fn test_request_id_middleware() { + // Create a simple app with our middleware + let app = Router::new() + .route("/test", get(test_handler)) + .layer(request_id_middleware()); + + // Test 1: Request without X-Request-ID header (should generate one) + let request = Request::builder().uri("/test").body(Body::empty()).unwrap(); + + let response = app.clone().oneshot(request).await.unwrap(); + + // Check that response has X-Request-ID header + let request_id = response.headers().get("X-Request-ID"); + assert!( + request_id.is_some(), + "Response should have X-Request-ID header" + ); + + let request_id_value = request_id.unwrap().to_str().unwrap(); + assert!( + !request_id_value.is_empty(), + "Request ID should not be empty" + ); + + // Verify it's a valid UUID + assert!( + Uuid::parse_str(request_id_value).is_ok(), + "Request ID should be a valid UUID" + ); + + // Test 2: Request with existing X-Request-ID header (should preserve it) + let existing_request_id = "test-request-id-12345"; + let request = Request::builder() + .uri("/test") + .header("X-Request-ID", existing_request_id) + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + + // Check that response has the same X-Request-ID header + let request_id = response.headers().get("X-Request-ID"); + assert!( + request_id.is_some(), + "Response should have X-Request-ID header" + ); + + let request_id_value = request_id.unwrap().to_str().unwrap(); + assert_eq!( + request_id_value, existing_request_id, + "Request ID should be preserved" + ); +} + +#[tokio::test] +async fn test_request_id_middleware_with_empty_header() { + // Create a simple app with our middleware + let app = Router::new() + .route("/test", get(test_handler)) + .layer(request_id_middleware()); + + // Test: Request with empty X-Request-ID header (should generate new one) + let request = Request::builder() + .uri("/test") + .header("X-Request-ID", "") + .body(Body::empty()) + .unwrap(); + + let response = app.oneshot(request).await.unwrap(); + + // Check that response has X-Request-ID header + let request_id = response.headers().get("X-Request-ID"); + assert!( + request_id.is_some(), + "Response should have X-Request-ID header" + ); + + let request_id_value = request_id.unwrap().to_str().unwrap(); + assert!( + !request_id_value.is_empty(), + "Request ID should not be empty" + ); + + // Verify it's a valid UUID (should generate new one since original was empty) + assert!( + Uuid::parse_str(request_id_value).is_ok(), + "Request ID should be a valid UUID" + ); +} diff --git a/backend/tests/test_sqlx_integration.rs b/backend/tests/test_sqlx_integration.rs index b22d187..94f27f8 100644 --- a/backend/tests/test_sqlx_integration.rs +++ b/backend/tests/test_sqlx_integration.rs @@ -1,42 +1,42 @@ -use sqlx::PgPool; -use std::env; - -#[tokio::test] -async fn test_database_connection_and_migration() { - dotenvy::dotenv().ok(); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set for tests"); - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to DB"); - // Check connection - let row: (i32,) = sqlx::query_as("SELECT 1") - .fetch_one(&pool) - .await - .expect("Ping failed"); - assert_eq!(row.0, 1); - // Check if market_category table exists - let exists: (bool,) = sqlx::query_as( - "SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'market_category' - )", - ) - .fetch_one(&pool) - .await - .expect("Failed to check table existence"); - assert!(exists.0, "market_category table should exist"); - // Insert and fetch a market_category - let inserted: (i32, String) = - sqlx::query_as("INSERT INTO market_category (name) VALUES ($1) RETURNING id, name") - .bind("TestCategory") - .fetch_one(&pool) - .await - .expect("Insert failed"); - assert_eq!(inserted.1, "TestCategory"); - // Clean up - sqlx::query("DELETE FROM market_category WHERE id = $1") - .bind(inserted.0) - .execute(&pool) - .await - .expect("Cleanup failed"); -} +use sqlx::PgPool; +use std::env; + +#[tokio::test] +async fn test_database_connection_and_migration() { + dotenvy::dotenv().ok(); + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set for tests"); + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to DB"); + // Check connection + let row: (i32,) = sqlx::query_as("SELECT 1") + .fetch_one(&pool) + .await + .expect("Ping failed"); + assert_eq!(row.0, 1); + // Check if market_category table exists + let exists: (bool,) = sqlx::query_as( + "SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'market_category' + )", + ) + .fetch_one(&pool) + .await + .expect("Failed to check table existence"); + assert!(exists.0, "market_category table should exist"); + // Insert and fetch a market_category + let inserted: (i32, String) = + sqlx::query_as("INSERT INTO market_category (name) VALUES ($1) RETURNING id, name") + .bind("TestCategory") + .fetch_one(&pool) + .await + .expect("Insert failed"); + assert_eq!(inserted.1, "TestCategory"); + // Clean up + sqlx::query("DELETE FROM market_category WHERE id = $1") + .bind(inserted.0) + .execute(&pool) + .await + .expect("Cleanup failed"); +} diff --git a/backend/tests/test_validator_api.rs b/backend/tests/test_validator_api.rs index 6c3367f..513f9b9 100644 --- a/backend/tests/test_validator_api.rs +++ b/backend/tests/test_validator_api.rs @@ -1,314 +1,314 @@ -use axum::Router; -use backend::{AppState, db::database::Database}; -use sqlx::PgPool; - -/// Test database configuration -struct TestDb { - pool: PgPool, -} - -impl TestDb { - /// Create a new test database connection - async fn new() -> Self { - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); - - let pool = PgPool::connect(&database_url) - .await - .expect("Failed to connect to test database"); - - // Run migrations - Self::run_migrations(&pool).await; - - Self { pool } - } - - /// Run database migrations - async fn run_migrations(pool: &PgPool) { - sqlx::migrate!("./migrations") - .run(pool) - .await - .expect("Failed to run migrations"); - } -} - -/// Create a test app with basic routes -fn create_test_app(pool: PgPool) -> Router { - let db = Database::from_pool(pool); - let state = AppState { db }; - - Router::new().with_state(state) -} - -#[tokio::test] -async fn test_validator_database_setup() { - // Test that we can set up a test database - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Test a simple query - let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 1); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_validator_app_creation() { - // Test that we can create a test app - let test_db = TestDb::new().await; - - // Create test app - let _app = create_test_app(test_db.pool.clone()); - - // Test that app was created successfully - assert!(true, "Test app creation successful"); -} - -#[tokio::test] -async fn test_validator_data_persistence() { - // Test that we can create and persist validator data - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Create a validator - let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; - let is_active = true; - - let result = sqlx::query( - "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" - ) - .bind(contract_address) - .bind(is_active) - .fetch_one(&mut *tx) - .await; - - assert!(result.is_ok(), "Failed to insert validator into database"); - - // Verify the validator was persisted - let validator_contract_address = sqlx::query_scalar::<_, String>( - "SELECT contract_address FROM validators WHERE contract_address = $1", - ) - .bind(contract_address) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - !validator_contract_address.is_empty(), - "Validator contract address should not be empty" - ); - - // Verify validator data directly from transaction - let db_validator_result = sqlx::query_scalar::<_, String>( - "SELECT json_build_object('contract_address', contract_address, 'is_active', is_active)::text FROM validators WHERE contract_address = $1" - ) - .bind(contract_address) - .fetch_optional(&mut *tx) - .await; - - assert!( - db_validator_result.is_ok(), - "Validator query should succeed" - ); - let db_validator_json = db_validator_result.unwrap(); - assert!( - db_validator_json.is_some(), - "Validator should be retrievable from database" - ); - - let validator: serde_json::Value = serde_json::from_str(&db_validator_json.unwrap()).unwrap(); - assert_eq!(validator["contract_address"], contract_address); - assert_eq!(validator["is_active"], is_active); - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Validator data persistence test passed"); -} - -#[tokio::test] -async fn test_validator_migrations() { - // Test that migrations run successfully - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - // Check that validators table exists - let table_exists: bool = sqlx::query_scalar( - "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'validators')", - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - assert!( - table_exists, - "Validators table should exist after migrations" - ); - - // Check table structure - let columns = sqlx::query_scalar::<_, String>( - "SELECT string_agg(column_name, ', ' ORDER BY ordinal_position) FROM information_schema.columns WHERE table_name = 'validators'" - ) - .fetch_one(&mut *tx) - .await - .unwrap(); - - // Verify key columns exist - assert!( - columns.contains("contract_address"), - "Validators table should have contract_address column" - ); - assert!( - columns.contains("is_active"), - "Validators table should have is_active column" - ); - assert!( - columns.contains("registered_at"), - "Validators table should have registered_at column" - ); - assert!( - columns.contains("updated_at"), - "Validators table should have updated_at column" - ); - - // Transaction auto-rollbacks, no cleanup needed -} - -#[tokio::test] -async fn test_validator_contract_address_uniqueness() { - // Test that contract addresses must be unique - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; - - // Insert first validator - let result1 = sqlx::query( - "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" - ) - .bind(contract_address) - .bind(true) - .fetch_one(&mut *tx) - .await; - - assert!( - result1.is_ok(), - "First validator should be inserted successfully" - ); - - // Try to insert second validator with same contract address - let result2 = sqlx::query( - "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" - ) - .bind(contract_address) - .bind(false) - .fetch_one(&mut *tx) - .await; - - // This should fail due to unique constraint - assert!( - result2.is_err(), - "Second validator with same contract address should fail" - ); - - // Since the transaction is now aborted, we need to verify the constraint worked - // by checking that the error contains the expected constraint violation - if let Err(e) = result2 { - let error_string = e.to_string(); - assert!( - error_string.contains("duplicate key") || error_string.contains("unique constraint"), - "Error should indicate duplicate key or unique constraint violation, got: {}", - error_string - ); - } - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Validator contract address uniqueness test passed"); -} - -#[tokio::test] -async fn test_validator_status_updates() { - // Test that we can update validator status - let test_db = TestDb::new().await; - - // Start transaction for test isolation - let mut tx = test_db - .pool - .begin() - .await - .expect("Failed to start transaction"); - - let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; - - // Insert validator with active status - let result = sqlx::query( - "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" - ) - .bind(contract_address) - .bind(true) - .fetch_one(&mut *tx) - .await; - - assert!(result.is_ok(), "Failed to insert validator"); - - // Update status to inactive - let update_result = - sqlx::query("UPDATE validators SET is_active = $1 WHERE contract_address = $2") - .bind(false) - .bind(contract_address) - .execute(&mut *tx) - .await; - - assert!(update_result.is_ok(), "Failed to update validator status"); - - // Verify status was updated directly from transaction - let db_validator_result = sqlx::query_scalar::<_, String>( - "SELECT json_build_object('contract_address', contract_address, 'is_active', is_active)::text FROM validators WHERE contract_address = $1" - ) - .bind(contract_address) - .fetch_optional(&mut *tx) - .await; - - assert!( - db_validator_result.is_ok(), - "Validator query should succeed" - ); - let db_validator_json = db_validator_result.unwrap(); - assert!( - db_validator_json.is_some(), - "Validator should still be retrievable" - ); - - let validator: serde_json::Value = serde_json::from_str(&db_validator_json.unwrap()).unwrap(); - assert_eq!( - validator["is_active"], false, - "Validator status should be updated to inactive" - ); - - // Transaction auto-rollbacks, no cleanup needed - println!("βœ… Validator status update test passed"); -} +use axum::Router; +use backend::{AppState, db::database::Database}; +use sqlx::PgPool; + +/// Test database configuration +struct TestDb { + pool: PgPool, +} + +impl TestDb { + /// Create a new test database connection + async fn new() -> Self { + let database_url = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/testdb".to_string()); + + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to test database"); + + // Run migrations + Self::run_migrations(&pool).await; + + Self { pool } + } + + /// Run database migrations + async fn run_migrations(pool: &PgPool) { + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations"); + } +} + +/// Create a test app with basic routes +fn create_test_app(pool: PgPool) -> Router { + let db = Database::from_pool(pool); + let state = AppState { db }; + + Router::new().with_state(state) +} + +#[tokio::test] +async fn test_validator_database_setup() { + // Test that we can set up a test database + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Test a simple query + let result: Result = sqlx::query_scalar("SELECT 1").fetch_one(&mut *tx).await; + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_validator_app_creation() { + // Test that we can create a test app + let test_db = TestDb::new().await; + + // Create test app + let _app = create_test_app(test_db.pool.clone()); + + // Test that app was created successfully + assert!(true, "Test app creation successful"); +} + +#[tokio::test] +async fn test_validator_data_persistence() { + // Test that we can create and persist validator data + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Create a validator + let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; + let is_active = true; + + let result = sqlx::query( + "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" + ) + .bind(contract_address) + .bind(is_active) + .fetch_one(&mut *tx) + .await; + + assert!(result.is_ok(), "Failed to insert validator into database"); + + // Verify the validator was persisted + let validator_contract_address = sqlx::query_scalar::<_, String>( + "SELECT contract_address FROM validators WHERE contract_address = $1", + ) + .bind(contract_address) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + !validator_contract_address.is_empty(), + "Validator contract address should not be empty" + ); + + // Verify validator data directly from transaction + let db_validator_result = sqlx::query_scalar::<_, String>( + "SELECT json_build_object('contract_address', contract_address, 'is_active', is_active)::text FROM validators WHERE contract_address = $1" + ) + .bind(contract_address) + .fetch_optional(&mut *tx) + .await; + + assert!( + db_validator_result.is_ok(), + "Validator query should succeed" + ); + let db_validator_json = db_validator_result.unwrap(); + assert!( + db_validator_json.is_some(), + "Validator should be retrievable from database" + ); + + let validator: serde_json::Value = serde_json::from_str(&db_validator_json.unwrap()).unwrap(); + assert_eq!(validator["contract_address"], contract_address); + assert_eq!(validator["is_active"], is_active); + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Validator data persistence test passed"); +} + +#[tokio::test] +async fn test_validator_migrations() { + // Test that migrations run successfully + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + // Check that validators table exists + let table_exists: bool = sqlx::query_scalar( + "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'validators')", + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + assert!( + table_exists, + "Validators table should exist after migrations" + ); + + // Check table structure + let columns = sqlx::query_scalar::<_, String>( + "SELECT string_agg(column_name, ', ' ORDER BY ordinal_position) FROM information_schema.columns WHERE table_name = 'validators'" + ) + .fetch_one(&mut *tx) + .await + .unwrap(); + + // Verify key columns exist + assert!( + columns.contains("contract_address"), + "Validators table should have contract_address column" + ); + assert!( + columns.contains("is_active"), + "Validators table should have is_active column" + ); + assert!( + columns.contains("registered_at"), + "Validators table should have registered_at column" + ); + assert!( + columns.contains("updated_at"), + "Validators table should have updated_at column" + ); + + // Transaction auto-rollbacks, no cleanup needed +} + +#[tokio::test] +async fn test_validator_contract_address_uniqueness() { + // Test that contract addresses must be unique + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; + + // Insert first validator + let result1 = sqlx::query( + "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" + ) + .bind(contract_address) + .bind(true) + .fetch_one(&mut *tx) + .await; + + assert!( + result1.is_ok(), + "First validator should be inserted successfully" + ); + + // Try to insert second validator with same contract address + let result2 = sqlx::query( + "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" + ) + .bind(contract_address) + .bind(false) + .fetch_one(&mut *tx) + .await; + + // This should fail due to unique constraint + assert!( + result2.is_err(), + "Second validator with same contract address should fail" + ); + + // Since the transaction is now aborted, we need to verify the constraint worked + // by checking that the error contains the expected constraint violation + if let Err(e) = result2 { + let error_string = e.to_string(); + assert!( + error_string.contains("duplicate key") || error_string.contains("unique constraint"), + "Error should indicate duplicate key or unique constraint violation, got: {}", + error_string + ); + } + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Validator contract address uniqueness test passed"); +} + +#[tokio::test] +async fn test_validator_status_updates() { + // Test that we can update validator status + let test_db = TestDb::new().await; + + // Start transaction for test isolation + let mut tx = test_db + .pool + .begin() + .await + .expect("Failed to start transaction"); + + let contract_address = "0x1234567890abcdef1234567890abcdef12345678"; + + // Insert validator with active status + let result = sqlx::query( + "INSERT INTO validators (contract_address, is_active) VALUES ($1, $2) RETURNING contract_address" + ) + .bind(contract_address) + .bind(true) + .fetch_one(&mut *tx) + .await; + + assert!(result.is_ok(), "Failed to insert validator"); + + // Update status to inactive + let update_result = + sqlx::query("UPDATE validators SET is_active = $1 WHERE contract_address = $2") + .bind(false) + .bind(contract_address) + .execute(&mut *tx) + .await; + + assert!(update_result.is_ok(), "Failed to update validator status"); + + // Verify status was updated directly from transaction + let db_validator_result = sqlx::query_scalar::<_, String>( + "SELECT json_build_object('contract_address', contract_address, 'is_active', is_active)::text FROM validators WHERE contract_address = $1" + ) + .bind(contract_address) + .fetch_optional(&mut *tx) + .await; + + assert!( + db_validator_result.is_ok(), + "Validator query should succeed" + ); + let db_validator_json = db_validator_result.unwrap(); + assert!( + db_validator_json.is_some(), + "Validator should still be retrievable" + ); + + let validator: serde_json::Value = serde_json::from_str(&db_validator_json.unwrap()).unwrap(); + assert_eq!( + validator["is_active"], false, + "Validator status should be updated to inactive" + ); + + // Transaction auto-rollbacks, no cleanup needed + println!("βœ… Validator status update test passed"); +} diff --git a/snfoundry.toml b/snfoundry.toml index 43fb5c1..58b2d01 100644 --- a/snfoundry.toml +++ b/snfoundry.toml @@ -1,33 +1,33 @@ -# Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html for more information - -# [sncast.myprofile1] # Define a profile name -# url = "http://127.0.0.1:5050/" # Url of the RPC provider -# accounts_file = "../account-file" # Path to the file with the account data -# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions -# keystore = "~/keystore" # Path to the keystore file -# wait_params = { timeout = 500, retry_interval = 10 } # Wait for submitted transaction parameters -# block_explorer = "StarkScan" # Block explorer service used to display links to transaction details - -[sncast.predifi] -account = "predifi" -accounts-file = "/home/akshola00/.starknet_accounts/starknet_open_zeppelin_accounts.json" -url = "https://free-rpc.nethermind.io/sepolia-juno/" - -# 0x4287a17fc57912782dbcbf43b9cdf8b9d8133e275ded2f03bd2617c3fa1974d -[sncast.predifi2] -account = "predifi2" -accounts-file = "/home/codespace/.starknet_accounts/starknet_open_zeppelin_accounts.json" -url = "https://free-rpc.nethermind.io/sepolia-juno/" - -[sncast.account-2] -account = "account-2" -accounts-file = "/Users/admin/.starknet_accounts/starknet_open_zeppelin_accounts.json" -url = "https://starknet-sepolia.public.blastapi.io" - -[sncast.default] -account = "account-2" - - -# SEPOLIA CONTRACT ADDRESS OF PREDIFI -# contract_address: 0x06ff646a722404885793669af5270d4285a8acbb6e7193332ad390844f300121 +# Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html for more information + +# [sncast.myprofile1] # Define a profile name +# url = "http://127.0.0.1:5050/" # Url of the RPC provider +# accounts_file = "../account-file" # Path to the file with the account data +# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions +# keystore = "~/keystore" # Path to the keystore file +# wait_params = { timeout = 500, retry_interval = 10 } # Wait for submitted transaction parameters +# block_explorer = "StarkScan" # Block explorer service used to display links to transaction details + +[sncast.predifi] +account = "predifi" +accounts-file = "/home/akshola00/.starknet_accounts/starknet_open_zeppelin_accounts.json" +url = "https://free-rpc.nethermind.io/sepolia-juno/" + +# 0x4287a17fc57912782dbcbf43b9cdf8b9d8133e275ded2f03bd2617c3fa1974d +[sncast.predifi2] +account = "predifi2" +accounts-file = "/home/codespace/.starknet_accounts/starknet_open_zeppelin_accounts.json" +url = "https://free-rpc.nethermind.io/sepolia-juno/" + +[sncast.account-2] +account = "account-2" +accounts-file = "/Users/admin/.starknet_accounts/starknet_open_zeppelin_accounts.json" +url = "https://starknet-sepolia.public.blastapi.io" + +[sncast.default] +account = "account-2" + + +# SEPOLIA CONTRACT ADDRESS OF PREDIFI +# contract_address: 0x06ff646a722404885793669af5270d4285a8acbb6e7193332ad390844f300121 # contract: https://sepolia.starkscan.co/contract/0x06ff646a722404885793669af5270d4285a8acbb6e7193332ad390844f300121 \ No newline at end of file diff --git a/src/STRK.cairo b/src/STRK.cairo index f9b40c7..11ebe76 100644 --- a/src/STRK.cairo +++ b/src/STRK.cairo @@ -88,7 +88,7 @@ pub mod STARKTOKEN { /// @param self The contract state. /// @return The number of decimals. fn decimals(self: @ContractState) -> u8 { - self.custom_decimals.read() // Return custom value + self.custom_decimals.read() // Return custom value } } diff --git a/src/base/events.cairo b/src/base/events.cairo index 112eb43..47804be 100644 --- a/src/base/events.cairo +++ b/src/base/events.cairo @@ -65,6 +65,8 @@ pub mod Events { pub amount: u256, } + + /// @notice Emitted when a pool changes state. /// @notice Emitted when a pool changes state.\ /// @param pool_id Unique identifier of the pool /// @param previous_status The previous status of the pool @@ -367,4 +369,24 @@ pub mod Events { pub amount: u256, pub timestamp: u64, } + + #[derive(Drop, starknet::Event)] + pub struct ValidatorSlashed { + #[key] + pub validator: ContractAddress, + pub amount: u256, + pub reputation_after: u256, + } + #[derive(Drop, starknet::Event)] + pub struct ValidatorPerformanceUpdated { + #[key] + pub validator: ContractAddress, + pub success: bool, + pub reputation_after: u256, + } + #[derive(Drop, starknet::Event)] + pub struct FeesDistributed { + pub pool_id: u256, + pub total_distributed: u256, + } } diff --git a/src/base/types.cairo b/src/base/types.cairo index 54e3970..9f20b47 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -1,3 +1,7 @@ +use starknet::ContractAddress; +use starknet::storage::Vec; +//use crate::base::types::Status; +//use crate::base::enums::{Category,Pool}; /// @notice Enum representing the types of pools available. #[derive(Copy, Drop, Serde, PartialEq, starknet::Store, Debug)] pub enum Pool { @@ -265,6 +269,11 @@ pub struct PoolDetails { pub initial_share_price: u16, /// @notice Whether the pool exists. pub exists: bool, + pub has_ended: bool, + pub is_resolved: bool, + pub correct_option: bool, + pub fee_pool: u256, + pub validators_count: u32, } // Emergency Types diff --git a/src/interfaces/ipredifi.cairo b/src/interfaces/ipredifi.cairo index 0476ea4..6c01601 100644 --- a/src/interfaces/ipredifi.cairo +++ b/src/interfaces/ipredifi.cairo @@ -118,6 +118,7 @@ pub trait IPredifi { /// @notice Collects the pool creation fee from the creator. /// @dev Transfers 1 STRK from creator to contract. + /// @param creator The creator's address. /// @param pool_id The pool ID for which the fee is being collected. fn collect_pool_creation_fee(ref self: TContractState, creator: ContractAddress, pool_id: u256); @@ -231,6 +232,7 @@ pub trait IPredifi { /// @notice Returns all pools in emergency state. /// @return Array of PoolDetails for emergency pools. fn get_emergency_pools(self: @TContractState) -> Array; + } #[starknet::interface] @@ -392,4 +394,31 @@ pub trait IPredifiValidator { /// @notice Unpauses the contract and resumes normal operations. /// @dev Can only be called by admin. Emits Unpaused event on success. fn unpause(ref self: TContractState); + + ///@notice Updates the validator's reputation and success/fail counts. + /// @param validator The validator address. + fn update_performance(ref self: TContractState, validator: ContractAddress, success: bool); + + /// @notice Slash a validator by halving reputation and treasury. + /// @param validator Validator address to slash. + fn slash_validator(ref self: TContractState, validator: ContractAddress); + + /// @notice Distribute fees among validators of a pool who chose the correct option. + /// @param pool_id ID of the pool to distribute fees for. + fn distribute_validator_fees(ref self: TContractState, pool_id: u256); + + /// @notice Get validator reputation. + fn get_validator_reputation(self: @TContractState, validator: ContractAddress) -> u256; + + /// @notice Get validator success count. + fn get_validator_success(self: @TContractState, validator: ContractAddress) ->u256; + + /// @notice Get validator fail count. + fn get_validator_slashed(self: @TContractState, validator: ContractAddress) ->u256; + + /// @notice Get validator treasury. + fn get_validator_treasury(self: @TContractState, validator: ContractAddress) ->u256; } + + + diff --git a/src/predifi.cairo b/src/predifi.cairo index aa4e73d..3db8375 100644 --- a/src/predifi.cairo +++ b/src/predifi.cairo @@ -1,6 +1,7 @@ #[starknet::contract] pub mod Predifi { // Cairo imports + use core::array::{Array, ArrayTrait, SpanTrait}; use core::hash::{HashStateExTrait, HashStateTrait}; use core::panic_with_felt252; use core::pedersen::PedersenTrait; @@ -20,17 +21,15 @@ pub mod Predifi { }; use crate::base::errors::Errors; use crate::base::events::Events::{ - BetPlaced, ContractPaused, ContractUnpaused, CreatorFeesCollected, DisputeRaised, - DisputeResolved, EmergencyActionCancelled, EmergencyActionExecuted, - EmergencyActionScheduled, EmergencyWithdrawal, FeeWithdrawn, FeesCollected, - PoolAutomaticallySettled, PoolCancelled, PoolCreated, PoolCreationFeeCollected, + BetPlaced, DisputeRaised, DisputeResolved, EmergencyActionCancelled, + EmergencyActionExecuted, EmergencyActionScheduled, EmergencyWithdrawal, FeeWithdrawn, + FeesCollected, FeesDistributed, PoolAutomaticallySettled, PoolCancelled, PoolEmergencyFrozen, PoolEmergencyResolved, PoolEmergencyUnfrozen, PoolResolved, - PoolStateTransition, PoolSuspended, ProtocolFeesCollected, StakeRefunded, UserStaked, - ValidatorAdded, ValidatorConfirmationsUpdated, ValidatorFeesDistributed, ValidatorRemoved, - ValidatorResultSubmitted, ValidatorsAssigned, + PoolStateTransition, PoolSuspended, StakeRefunded, UserStaked, ValidatorAdded, + ValidatorPerformanceUpdated, ValidatorRemoved, ValidatorResultSubmitted, ValidatorSlashed, + ValidatorsAssigned, }; use crate::base::security::{Security, SecurityTrait}; - // package imports use crate::base::types::{ CategoryType, EmergencyAction, EmergencyActionStatus, EmergencyActionType, @@ -155,6 +154,13 @@ pub mod Predifi { upgradeable: UpgradeableComponent::Storage, #[substorage(v0)] reentrancy_guard: ReentrancyGuardComponent::Storage, + //validator performance tracking + validator_reputation: Map, + validator_success_count: Map, + validator_fail_count: Map, + validator_slashed: Map, + pool_validators: Map<(u256, u32), ContractAddress>, + pool_validators_count: Map, } /// @notice Events emitted by the Predifi contract. @@ -238,8 +244,12 @@ pub mod Predifi { UpgradeableEvent: UpgradeableComponent::Event, #[flat] ReentrancyGuardEvent: ReentrancyGuardComponent::Event, + ValidatorSlashed: ValidatorSlashed, + ValidatorPerformanceUpdated: ValidatorPerformanceUpdated, + FeesDistributed: FeesDistributed, } + #[derive(Drop, Hash)] struct HashingProperties { username: felt252, @@ -251,6 +261,129 @@ pub mod Predifi { id: felt252, login: HashingProperties, } + /// @notice Update validator performance and adjust reputation. + /// @param validator Address of the validator. + /// @param success True if validation was correct, false otherwise. + fn update_performance(ref self: ContractState, validator: ContractAddress, success: bool) { + if (success) { + let prev: u256 = self.validator_success_count.read(validator); + self.validator_success_count.write(validator, prev + 1); + + let rep: u256 = self.validator_reputation.read(validator) + 1; + self.validator_reputation.write(validator, rep); + + self + .emit( + Event::ValidatorPerformanceUpdated( + ValidatorPerformanceUpdated { validator, success, reputation_after: rep }, + ), + ); + } else { + let prev: u256 = self.validator_fail_count.read(validator); + self.validator_fail_count.write(validator, prev + 1); + + let rep: u256 = self.validator_reputation.read(validator); + let new_rep: u256 = if rep > 0 { + rep - 1 + } else { + 0 + }; + self.validator_reputation.write(validator, new_rep); + + self + .emit( + Event::ValidatorPerformanceUpdated( + ValidatorPerformanceUpdated { + validator, success, reputation_after: new_rep, + }, + ), + ); + } + } + + /// @notice Slash a validator by halving reputation and treasury. + /// @param validator Validator address to slash. + fn slash_validator(ref self: ContractState, validator: ContractAddress) { + let reputation: u256 = self.validator_reputation.read(validator); + let treasury: u256 = self.validator_treasuries.read(validator); + + let new_rep: u256 = reputation / 2; + let new_treasury: u256 = treasury / 2; + + self.validator_reputation.write(validator, new_rep); + self.validator_treasuries.write(validator, new_treasury); + + self + .emit( + Event::ValidatorSlashed( + ValidatorSlashed { + validator, amount: treasury - new_treasury, reputation_after: new_rep, + }, + ), + ); + } + + /// @notice Distribute fees among to validators of a pool who chose the correct option. + /// @param pool_id ID fo the pool to distribute fees for. + fn distribute_validator_fees(ref self: ContractState, pool_id: u256) { + //Ensure pool exists + let pool_data: PoolDetails = self.pools.read(pool_id); + + //Ensure pool has ended + assert(pool_data.has_ended == true, 1002); + + //Ensure pool has been resolved + assert(pool_data.is_resolved == true, 1003); + + let correct_option = pool_data.correct_option; + let total_fees: u256 = pool_data.fee_pool; + + //Filter validators that chose the correct option + let mut eligible_validators = ArrayTrait::new(); + let mut total_reputation: u256 = 0; + + let len = pool_data.validators_count; + let mut i = 0; + while i < len { + let validator = self.pool_validators.read((pool_id, i)); + + // Use your pool_validation_results storage + let choice: bool = self.pool_validation_results.read((pool_id, validator)); + if choice == correct_option { + let rep: u256 = self.validator_reputation.read(validator); + eligible_validators.append(validator); + total_reputation += rep; + } + i += 1; + } + //Distribute proportionally by reputation + for validator in eligible_validators { + //let validator=*validator_ptr; + let rep: u256 = self.validator_reputation.read(validator); + let share: u256 = total_fees * rep / total_reputation; + + let balance: u256 = self.validator_treasuries.read(validator); + self.validator_treasuries.write(validator, balance + share); + } + self.emit(FeesDistributed { pool_id, total_distributed: total_fees }); + } + + /// @notice Get validator reputation + fn get_validator_reputation(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_reputation.read(validator) + } + /// @notice Get validator success count + fn get_validator_success(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_success_count.read(validator) + } + /// @notice Get validator fail count + fn get_validator_slashed(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_slashed.read(validator) + } + /// @notice Get validator treasury + fn get_validator_treasury(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_treasuries.read(validator) + } /// @notice Initializes the Predifi contract. /// @param self The contract state. @@ -329,6 +462,8 @@ pub mod Predifi { pool_id = self.generate_deterministic_number(); } + //pub validators: Array, + // Create pool details structure let pool_details = PoolDetails { pool_id: pool_id, @@ -358,6 +493,11 @@ pub mod Predifi { totalSharesOption2: 0_u256, initial_share_price: 5000, // 0.5 in basis points (10000 = 1.0) exists: true, + has_ended: false, + is_resolved: false, + correct_option: false, + fee_pool: 0_u256, + validators_count: 0, }; self.pools.write(pool_id, pool_details); @@ -1254,6 +1394,8 @@ pub mod Predifi { } emergency_pools } + + } #[abi(embed_v0)] @@ -2077,6 +2219,7 @@ pub mod Predifi { result } + /// @notice Calculates the validation consensus for a pool. /// @param pool_id The pool ID. /// @param total_validations The total number of validations. @@ -2413,5 +2556,129 @@ pub mod Predifi { ), ); } + /// @notice Update validator performance and adjust reputation. + /// @param validator Address of the validator. + /// @param success True if validation was correct, false otherwise. + fn update_performance(ref self: ContractState, validator: ContractAddress, success: bool) { + if (success) { + let prev: u256 = self.validator_success_count.read(validator); + self.validator_success_count.write(validator, prev + 1); + + let rep: u256 = self.validator_reputation.read(validator) + 1; + self.validator_reputation.write(validator, rep); + + self + .emit( + Event::ValidatorPerformanceUpdated( + ValidatorPerformanceUpdated { validator, success, reputation_after: rep }, + ), + ); + } else { + let prev: u256 = self.validator_fail_count.read(validator); + self.validator_fail_count.write(validator, prev + 1); + + let rep: u256 = self.validator_reputation.read(validator); + let new_rep: u256 = if rep > 0 { + rep - 1 + } else { + 0 + }; + self.validator_reputation.write(validator, new_rep); + + self + .emit( + Event::ValidatorPerformanceUpdated( + ValidatorPerformanceUpdated { + validator, success, reputation_after: new_rep, + }, + ), + ); + } + } + + /// @notice Slash a validator by halving reputation and treasury. + /// @param validator Validator address to slash. + fn slash_validator(ref self: ContractState, validator: ContractAddress) { + let reputation: u256 = self.validator_reputation.read(validator); + let treasury: u256 = self.validator_treasuries.read(validator); + + let new_rep: u256 = reputation / 2; + let new_treasury: u256 = treasury / 2; + + self.validator_reputation.write(validator, new_rep); + self.validator_treasuries.write(validator, new_treasury); + + self + .emit( + Event::ValidatorSlashed( + ValidatorSlashed { + validator, amount: treasury - new_treasury, reputation_after: new_rep, + }, + ), + ); + } + + /// @notice Distribute fees among to validators of a pool who chose the correct option. + /// @param pool_id ID fo the pool to distribute fees for. + fn distribute_validator_fees(ref self: ContractState, pool_id: u256) { + //Ensure pool exists + let pool_data: PoolDetails = self.pools.read(pool_id); + + //Ensure pool has ended + assert(pool_data.has_ended == true, 1002); + + //Ensure pool has been resolved + assert(pool_data.is_resolved == true, 1003); + + let correct_option = pool_data.correct_option; + let total_fees: u256 = pool_data.fee_pool; + + //Filter validators that chose the correct option + let mut eligible_validators = ArrayTrait::new(); + let mut total_reputation: u256 = 0; + + let len = pool_data.validators_count; + let mut i = 0; + while i < len { + let validator = self.pool_validators.read((pool_id, i)); + + // Use your pool_validation_results storage + let choice: bool = self.pool_validation_results.read((pool_id, validator)); + if choice == correct_option { + let rep: u256 = self.validator_reputation.read(validator); + eligible_validators.append(validator); + total_reputation += rep; + } + i += 1; + } + //Distribute proportionally by reputation + for validator in eligible_validators { + //let validator=*validator_ptr; + let rep: u256 = self.validator_reputation.read(validator); + let share: u256 = total_fees * rep / total_reputation; + + let balance: u256 = self.validator_treasuries.read(validator); + self.validator_treasuries.write(validator, balance + share); + } + self.emit(FeesDistributed { pool_id, total_distributed: total_fees }); + } + + /// @notice Get validator reputation + fn get_validator_reputation(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_reputation.read(validator) + } + /// @notice Get validator success count + fn get_validator_success(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_success_count.read(validator) + } + /// @notice Get validator fail count + fn get_validator_slashed(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_slashed.read(validator) + } + /// @notice Get validator treasury + fn get_validator_treasury(self: @ContractState, validator: ContractAddress) -> u256 { + self.validator_treasuries.read(validator) + } + } } diff --git a/src/utils.cairo b/src/utils.cairo index 3434238..e6475e4 100644 --- a/src/utils.cairo +++ b/src/utils.cairo @@ -7,7 +7,7 @@ pub mod Utils { use starknet::{ContractAddress, get_caller_address}; use crate::interfaces::iUtils::IUtility; - const STRK_USD: felt252 = 6004514686061859652; // STRK/USD in felt252 + const STRK_USD: felt252 = 6004514686061859652; // STRK/USD in felt252 /// @notice Storage struct for Utils contract. /// @dev Holds the owner and the pragma contract address. diff --git a/tests/integration_test.cairo b/tests/integration_test.cairo index aa8d4fe..7bda34a 100644 --- a/tests/integration_test.cairo +++ b/tests/integration_test.cairo @@ -66,7 +66,7 @@ fn create_default_pool(contract: IPredifiDispatcher) -> u256 { contract .create_pool( 'Example Pool', - 0, // 0 = WinBet + 0, // 0 = WinBet "A simple betting pool", "image.png", "event.com/details", @@ -102,7 +102,7 @@ fn get_default_pool_params() -> ( ) { ( 'Test Pool', - 0, // WinBet + 0, // WinBet "Test Description", "test.png", "test.com", @@ -271,7 +271,7 @@ fn test_pool_creation_staking_dispute_resolution_flow() { // Admin resolves dispute start_cheat_caller_address(dispute_contract.contract_address, admin); - dispute_contract.resolve_dispute(pool_id, true); // Set Team B as winner + dispute_contract.resolve_dispute(pool_id, true); // Set Team B as winner stop_cheat_caller_address(dispute_contract.contract_address); // Verify dispute resolution @@ -343,11 +343,11 @@ fn test_pool_voting_validation_settlement_flow() { // Validators submit validation results start_cheat_caller_address(validator_contract.contract_address, validator1); - validator_contract.validate_pool_result(pool_id, true); // Vote for Team B + validator_contract.validate_pool_result(pool_id, true); // Vote for Team B stop_cheat_caller_address(validator_contract.contract_address); start_cheat_caller_address(validator_contract.contract_address, validator2); - validator_contract.validate_pool_result(pool_id, true); // Vote for Team B + validator_contract.validate_pool_result(pool_id, true); // Vote for Team B stop_cheat_caller_address(validator_contract.contract_address); // Verify automatic settlement @@ -420,7 +420,7 @@ fn test_full_lifecycle_with_dispute_and_settlement() { // Admin resolves dispute start_cheat_caller_address(dispute_contract.contract_address, admin); - dispute_contract.resolve_dispute(pool_id, false); // Set Team A as winner + dispute_contract.resolve_dispute(pool_id, false); // Set Team A as winner stop_cheat_caller_address(dispute_contract.contract_address); // Advance time to end time for settlement @@ -473,13 +473,13 @@ fn test_validator_consensus_with_conflicting_votes() { let pool_id = contract .create_pool( 'Test Pool', - 0, // WinBet + 0, // WinBet "Test Description", "test.png", "test.com", - 1710000000, // start time - 1710003600, // lock time (1 hour later) - 1710007200, // end time (2 hours after lock) + 1710000000, // start time + 1710003600, // lock time (1 hour later) + 1710007200, // end time (2 hours after lock) 'Team A', 'Team B', 100, @@ -503,7 +503,7 @@ fn test_validator_consensus_with_conflicting_votes() { // First validator votes for Team B (true) start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_ONE); - validator_contract.validate_pool_result(pool_id, true); // Team B + validator_contract.validate_pool_result(pool_id, true); // Team B stop_cheat_caller_address(validator_contract.contract_address); // Pool should still be locked after first validation @@ -512,7 +512,7 @@ fn test_validator_consensus_with_conflicting_votes() { // Second validator votes for Team A (false) - creates conflict start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_TWO); - validator_contract.validate_pool_result(pool_id, false); // Team A + validator_contract.validate_pool_result(pool_id, false); // Team A stop_cheat_caller_address(validator_contract.contract_address); // Pool should still be locked after second validation (tie situation) @@ -521,7 +521,7 @@ fn test_validator_consensus_with_conflicting_votes() { // Third validator breaks the tie by voting for Team B (true) start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_THREE); - validator_contract.validate_pool_result(pool_id, true); // Team B - breaks tie + validator_contract.validate_pool_result(pool_id, true); // Team B - breaks tie stop_cheat_caller_address(validator_contract.contract_address); stop_cheat_block_timestamp(contract.contract_address); @@ -599,7 +599,7 @@ fn test_validator_consensus_with_default_confirmations() { // First validator votes for Team B start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_ONE); - validator_contract.validate_pool_result(pool_id, true); // Team B + validator_contract.validate_pool_result(pool_id, true); // Team B stop_cheat_caller_address(validator_contract.contract_address); // Pool should still be locked (need 2 confirmations) @@ -608,7 +608,7 @@ fn test_validator_consensus_with_default_confirmations() { // Second validator also votes for Team B (creates consensus) start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_TWO); - validator_contract.validate_pool_result(pool_id, true); // Team B - consensus reached + validator_contract.validate_pool_result(pool_id, true); // Team B - consensus reached stop_cheat_caller_address(validator_contract.contract_address); stop_cheat_block_timestamp(contract.contract_address); @@ -684,12 +684,12 @@ fn test_validator_conflict_with_two_confirmations() { // First validator votes Team B start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_ONE); - validator_contract.validate_pool_result(pool_id, true); // Team B + validator_contract.validate_pool_result(pool_id, true); // Team B stop_cheat_caller_address(validator_contract.contract_address); // Second validator votes Team A (conflict) start_cheat_caller_address(validator_contract.contract_address, VALIDATOR_TWO); - validator_contract.validate_pool_result(pool_id, false); // Team A + validator_contract.validate_pool_result(pool_id, false); // Team A stop_cheat_caller_address(validator_contract.contract_address); stop_cheat_block_timestamp(contract.contract_address); @@ -832,11 +832,11 @@ fn test_multiple_users_voting_and_natural_settlement() { let (validator1, validator2) = validator_contract.get_pool_validators(pool_id); start_cheat_caller_address(validator_contract.contract_address, validator1); - validator_contract.validate_pool_result(pool_id, false); // Team A wins + validator_contract.validate_pool_result(pool_id, false); // Team A wins stop_cheat_caller_address(validator_contract.contract_address); start_cheat_caller_address(validator_contract.contract_address, validator2); - validator_contract.validate_pool_result(pool_id, false); // Team A wins + validator_contract.validate_pool_result(pool_id, false); // Team A wins stop_cheat_caller_address(validator_contract.contract_address); // Verify settlement diff --git a/tests/test_dispute.cairo b/tests/test_dispute.cairo index c9419c7..ad09ac0 100644 --- a/tests/test_dispute.cairo +++ b/tests/test_dispute.cairo @@ -1,6 +1,7 @@ use contract::base::events::Events::{ ContractPaused, ContractUnpaused, DisputeRaised, StakeRefunded, -}; + +} use contract::base::types::Status; use contract::interfaces::ipredifi::{ IPredifiDispatcherTrait, IPredifiDisputeDispatcherTrait, IPredifiValidatorDispatcherTrait, @@ -11,8 +12,9 @@ use core::serde::Serde; use core::traits::TryInto; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{ - EventSpyAssertionsTrait, EventSpyTrait, spy_events, start_cheat_block_timestamp, - start_cheat_caller_address, stop_cheat_block_timestamp, stop_cheat_caller_address, + EventSpyAssertionsTrait, EventSpyTrait, spy_events, start_cheat_block_timestamp, + start_cheat_caller_address, + stop_cheat_block_timestamp, stop_cheat_caller_address, }; use starknet::{ContractAddress, get_block_timestamp}; use super::test_utils::{approve_tokens_for_payment, create_default_pool, deploy_predifi}; @@ -820,7 +822,6 @@ fn test_refund_stake_paused() { start_cheat_caller_address(contract.contract_address, admin); contract.refund_stake(1); } - #[test] fn test_contract_pause_unpause_events() { let (_, _, validator_contract, _, _) = deploy_predifi(); diff --git a/tests/test_emergency_functions.cairo b/tests/test_emergency_functions.cairo index d9a069d..f6a9de4 100644 --- a/tests/test_emergency_functions.cairo +++ b/tests/test_emergency_functions.cairo @@ -156,7 +156,6 @@ fn test_emergency_freeze_pool() { // Check that emergency events were emitted let events = spy.get_events(); assert(events.events.len() >= 2, 'Missing emergency events'); - // Verify pool is in emergency state assert!(dispatcher.is_pool_emergency_state(pool_id), "Pool should be in emergency state"); diff --git a/tests/test_pools.cairo b/tests/test_pools.cairo index e614b7f..04cd89a 100644 --- a/tests/test_pools.cairo +++ b/tests/test_pools.cairo @@ -29,9 +29,9 @@ use super::test_utils::{ fn test_create_pool() { let (contract, _, _, pool_creator, erc20_address) = deploy_predifi(); + // Setup event spy let mut spy = spy_events(); - // Approve the DISPATCHER contract to spend tokens start_cheat_caller_address(erc20_address, pool_creator); approve_tokens_for_payment( @@ -937,4 +937,6 @@ fn test_pool_state_transition_event() { assert(events.events.len() > 0, 'No state events'); stop_cheat_caller_address(contract.contract_address); + } + diff --git a/tests/test_user_participation.cairo b/tests/test_user_participation.cairo index b7f9849..eee2ac0 100644 --- a/tests/test_user_participation.cairo +++ b/tests/test_user_participation.cairo @@ -20,7 +20,8 @@ fn test_get_utils_owner() { let owner: ContractAddress = 'owner'.try_into().unwrap(); state.owner.write(owner); // setting the current owner's addrees - let retrieved_owner = state.get_owner(); // retrieving the owner's address from contract storage + let retrieved_owner = state + .get_owner(); // retrieving the owner's address from contract storage assert_eq!(retrieved_owner, owner); } @@ -432,7 +433,8 @@ fn test_distribute_validation_fee() { stop_cheat_caller_address(erc20_address); start_cheat_caller_address(dispatcher.contract_address, POOL_CREATOR); - dispatcher.collect_pool_creation_fee(POOL_CREATOR, 1); // Using pool_id 1 for test + + dispatcher.collect_pool_creation_fee(POOL_CREATOR,1); // Using pool_id 1 for test validator_dispatcher.calculate_validator_fee(18, 10_000); @@ -1378,4 +1380,6 @@ fn test_bet_placed_event() { assert(events.events.len() > 0, 'No bet events'); stop_cheat_caller_address(contract.contract_address); + } + diff --git a/tests/test_utils.cairo b/tests/test_utils.cairo index 31b1ff4..a3a904b 100644 --- a/tests/test_utils.cairo +++ b/tests/test_utils.cairo @@ -55,7 +55,7 @@ pub fn create_default_pool(contract: IPredifiDispatcher) -> u256 { contract .create_pool( 'Example Pool', - 0, // 0 = WinBet + 0, // 0 = WinBet "A simple betting pool", "image.png", "event.com/details", @@ -83,7 +83,7 @@ pub fn declare_contract(name: ByteArray) -> ClassHash { pub fn setup_user_with_tokens(user: ContractAddress, erc20_address: ContractAddress, amount: u256) { let erc20: IERC20Dispatcher = IERC20Dispatcher { contract_address: erc20_address }; start_cheat_caller_address(erc20_address, user); - erc20.approve(erc20_address, amount); // Approve the ERC20 contract itself to mint + erc20.approve(erc20_address, amount); // Approve the ERC20 contract itself to mint stop_cheat_caller_address(erc20_address); } @@ -128,7 +128,7 @@ pub fn get_default_pool_params() -> ( let current_time = get_block_timestamp(); ( 'Default Pool', - 0, // 0 = WinBet + 0, // 0 = WinBet "Default Description", "default_image.jpg", "https://example.com", @@ -156,7 +156,7 @@ pub fn create_test_pool( dispatcher .create_pool( poolName, - 0, // 0 = WinBet + 0, // 0 = WinBet "Test Description", "Test Image", "Test URL", diff --git a/tests/test_validators.cairo b/tests/test_validators.cairo index dbea3cb..a69e2d1 100644 --- a/tests/test_validators.cairo +++ b/tests/test_validators.cairo @@ -1,6 +1,9 @@ use contract::base::events::Events::{ - PoolResolved, ValidatorAdded, ValidatorRemoved, ValidatorResultSubmitted, -}; + + PoolResolved,ValidatorAdded, ValidatorRemoved, +ValidatorResultSubmitted,}; + + use contract::base::types::Status; use contract::interfaces::ipredifi::{ IPredifiDispatcherTrait, IPredifiValidator, IPredifiValidatorDispatcherTrait, @@ -12,8 +15,10 @@ use core::traits::{Into, TryInto}; use openzeppelin::access::accesscontrol::AccessControlComponent::InternalTrait as AccessControlInternalTrait; use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; use snforge_std::{ - EventSpyAssertionsTrait, EventSpyTrait, spy_events, start_cheat_block_timestamp, - start_cheat_caller_address, stop_cheat_block_timestamp, stop_cheat_caller_address, test_address, + + EventSpyAssertionsTrait,EventSpyTrait, spy_events, start_cheat_block_timestamp, + start_cheat_caller_address, stop_cheat_block_timestamp, stop_cheat_caller_address , test_address, + }; use starknet::storage::{MutableVecTrait, StoragePointerReadAccess}; use starknet::{ContractAddress, get_block_timestamp}; @@ -83,6 +88,10 @@ fn test_validate_pool_result_success() { start_cheat_caller_address(validator_contract.contract_address, validator1); validator_contract.validate_pool_result(pool_id, true); // Vote for option2 stop_cheat_caller_address(validator_contract.contract_address); + + // Check that ValidatorResultSubmitted event was emitted + let events = spy.get_events(); + assert(events.events.len() > 0, 'No validation events'); // Check that ValidatorResultSubmitted event was emitted let events = spy.get_events(); @@ -1043,3 +1052,47 @@ fn test_assign_validators_event() { let events = spy.get_events(); assert(events.events.len() > 0, 'No validator events'); } + +#[test] +fn test_update_performance_success_and_failure() { + let admin: ContractAddress = 0x1.into(); + let validator: ContractAddress = 0x2.into(); + let test_address: ContractAddress= 0x100.into(); + + //initialize contract state for testing + let mut state= Predifi::contract_state_for_testing(); + //let storage_state: @ContractState =store_contract_state_for_testing(&mut state); + + //Initialize access control and grant DEFAULT_ADMIN_ROLE to admin + let mut ac= state.accesscontrol; + AccessControlInternalTrait::initializer(ref ac); + AccessControlInternalTrait::_grant_role(ref ac, DEFAULT_ADMIN_ROLE,admin); + state.accesscontrol=ac; + + // act as admin to add the validator + start_cheat_caller_address(test_address, admin); + IPredifiValidator::add_validator(ref mut state, validator); + stop_cheat_caller_address(test_address); + + //update performance success + start_cheat_caller_address(test_address, validator); + IPredifiValidator::update_performance(ref mut state,validator, true); + stop_cheat_caller_address(test_address); + + let rep_after_success = IPredifiValidator::get_validator_reputation(ref state, validator); + let success_count = IPredifiValidator::get_validator_success(ref state,validator); + assert(rep_after_success == 11, "Reputation should increase by 1"); + assert(success_count == 1, "Succcess count should increase"); + + //update performance - failure + start_cheat_caller_address(test_address, validator); + IPredifiValidator::update_performance(ref mut state, validator, false); + stop_cheat_caller_address(test_address); + + let rep_after_failure = IPredifiValidator::get_validator_reputation( ref state, validator); + let fail_count = IPredifiValidator::get_validator_slashed( ref state, validator); + assert(rep_after_failure == 10, "Reputation should decrease by 1"); + assert(fail_count == 1, "Fail count should increase"); +} + +