diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b9c2a7a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + name: cargo fmt + continue-on-error: true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@1.91 + with: + components: rustfmt + - run: cargo fmt --manifest-path backend/Cargo.toml -- --check + - run: cargo fmt --manifest-path tracker/Cargo.toml -- --check + - run: cargo fmt --manifest-path zot/seed/Cargo.toml -- --check + + backend: + name: backend + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: backend + POSTGRES_PASSWORD: backend + POSTGRES_DB: backend + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U backend" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + env: + DATABASE_URL: postgres://backend:backend@localhost:5432/backend + defaults: + run: + working-directory: backend + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@1.91 + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: backend + - run: cargo clippy --all-targets --all-features -- -D warnings + continue-on-error: true + - run: cargo test --all-targets --all-features + continue-on-error: true + + tracker: + name: tracker + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: tracker + POSTGRES_PASSWORD: tracker + POSTGRES_DB: tracker + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U tracker" + --health-interval 5s + --health-timeout 3s + --health-retries 10 + env: + DATABASE_URL: postgres://tracker:tracker@localhost:5432/tracker + defaults: + run: + working-directory: tracker + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@1.91 + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: tracker + - run: cargo clippy --all-targets --all-features -- -D warnings + continue-on-error: true + - run: cargo test --all-features + continue-on-error: true diff --git a/.github/workflows/backend.yml b/.github/workflows/docker-backend.yml similarity index 59% rename from .github/workflows/backend.yml rename to .github/workflows/docker-backend.yml index d6c526b..007e492 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/docker-backend.yml @@ -1,8 +1,17 @@ -name: Backend +name: Docker Backend + permissions: + contents: read packages: write on: + push: + branches: [main] + paths: + - "backend/**" + - "docker/Dockerfile.backend" + - ".github/workflows/docker-backend.yml" + tags: ["v*"] workflow_dispatch: {} env: @@ -10,6 +19,8 @@ env: jobs: build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: @@ -18,16 +29,15 @@ jobs: runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Prepare platform pair + - name: Prepare platform name run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -40,14 +50,14 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile.backend platforms: ${{ matrix.platform }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=backend-${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=backend-${{ matrix.platform }} + cache-to: type=gha,scope=backend-${{ matrix.platform }},mode=max - name: Export digest run: | @@ -56,7 +66,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: digests-backend-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -64,11 +74,15 @@ jobs: retention-days: 1 merge: + name: Merge multi-arch manifest runs-on: ubuntu-latest - needs: build + needs: [build] steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download digests - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-backend-* @@ -84,11 +98,25 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=sha + - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests run: | - docker buildx imagetools create -t ${{ env.IMAGE }}:${{ github.sha }} \ - $(printf '${{ env.IMAGE }}@sha256:%s ' *) + docker buildx imagetools create \ + $(for tag in ${{ steps.meta.outputs.tags }}; do echo "-t $tag"; done) \ + $(printf "${IMAGE}@sha256:%s " *) - name: Inspect image - run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ github.sha }} + run: docker buildx imagetools inspect "${IMAGE}:sha-$(git rev-parse --short HEAD)" diff --git a/.github/workflows/frontend.yml b/.github/workflows/docker-frontend.yml similarity index 59% rename from .github/workflows/frontend.yml rename to .github/workflows/docker-frontend.yml index 4440f81..80f9431 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/docker-frontend.yml @@ -1,8 +1,17 @@ -name: Frontend +name: Docker Frontend + permissions: + contents: read packages: write on: + push: + branches: [main] + paths: + - "frontend/**" + - "docker/Dockerfile.frontend" + - ".github/workflows/docker-frontend.yml" + tags: ["v*"] workflow_dispatch: {} env: @@ -10,6 +19,8 @@ env: jobs: build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: @@ -18,16 +29,15 @@ jobs: runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Prepare platform pair + - name: Prepare platform name run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -40,14 +50,14 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile.frontend platforms: ${{ matrix.platform }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=frontend-${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=frontend-${{ matrix.platform }} + cache-to: type=gha,scope=frontend-${{ matrix.platform }},mode=max - name: Export digest run: | @@ -56,7 +66,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: digests-frontend-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -64,11 +74,15 @@ jobs: retention-days: 1 merge: + name: Merge multi-arch manifest runs-on: ubuntu-latest - needs: build + needs: [build] steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download digests - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-frontend-* @@ -84,11 +98,25 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=sha + - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests run: | - docker buildx imagetools create -t ${{ env.IMAGE }}:${{ github.sha }} \ - $(printf '${{ env.IMAGE }}@sha256:%s ' *) + docker buildx imagetools create \ + $(for tag in ${{ steps.meta.outputs.tags }}; do echo "-t $tag"; done) \ + $(printf "${IMAGE}@sha256:%s " *) - name: Inspect image - run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ github.sha }} + run: docker buildx imagetools inspect "${IMAGE}:sha-$(git rev-parse --short HEAD)" diff --git a/.github/workflows/tracker-publish.yml b/.github/workflows/docker-tracker.yml similarity index 59% rename from .github/workflows/tracker-publish.yml rename to .github/workflows/docker-tracker.yml index 9a35741..b8ad8a7 100644 --- a/.github/workflows/tracker-publish.yml +++ b/.github/workflows/docker-tracker.yml @@ -1,8 +1,17 @@ -name: Build Tracker Image +name: Docker Tracker + permissions: + contents: read packages: write on: + push: + branches: [main] + paths: + - "tracker/**" + - "docker/Dockerfile.tracker" + - ".github/workflows/docker-tracker.yml" + tags: ["v*"] workflow_dispatch: {} env: @@ -10,6 +19,8 @@ env: jobs: build: + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: @@ -18,16 +29,15 @@ jobs: runner: ubuntu-latest - platform: linux/arm64 runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Prepare platform pair + - name: Prepare platform name run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -40,14 +50,14 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile.tracker platforms: ${{ matrix.platform }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha,scope=tracker-${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=tracker-${{ matrix.platform }} + cache-to: type=gha,scope=tracker-${{ matrix.platform }},mode=max - name: Export digest run: | @@ -56,7 +66,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: digests-tracker-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -64,11 +74,15 @@ jobs: retention-days: 1 merge: + name: Merge multi-arch manifest runs-on: ubuntu-latest - needs: build + needs: [build] steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download digests - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-tracker-* @@ -84,11 +98,25 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=sha + - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests run: | - docker buildx imagetools create -t ${{ env.IMAGE }}:${{ github.sha }} \ - $(printf '${{ env.IMAGE }}@sha256:%s ' *) + docker buildx imagetools create \ + $(for tag in ${{ steps.meta.outputs.tags }}; do echo "-t $tag"; done) \ + $(printf "${IMAGE}@sha256:%s " *) - name: Inspect image - run: docker buildx imagetools inspect ${{ env.IMAGE }}:${{ github.sha }} + run: docker buildx imagetools inspect "${IMAGE}:sha-$(git rev-parse --short HEAD)" diff --git a/.github/workflows/tracker.yml b/.github/workflows/tracker.yml deleted file mode 100644 index 3046252..0000000 --- a/.github/workflows/tracker.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: tracker - -on: - push: - paths: - - 'tracker/**' - - '.github/workflows/tracker.yml' - pull_request: - paths: - - 'tracker/**' - - '.github/workflows/tracker.yml' - -jobs: - test: - runs-on: ubuntu-latest - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: tracker - POSTGRES_PASSWORD: tracker - POSTGRES_DB: tracker - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U tracker" - --health-interval 5s - --health-timeout 3s - --health-retries 10 - env: - DATABASE_URL: postgres://tracker:tracker@localhost:5432/tracker - defaults: - run: - working-directory: tracker - steps: - - uses: actions/checkout@v7 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - workspaces: tracker - - run: cargo build --all-targets - - run: cargo test --all-features diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c8491ce..dca6814 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -2,6 +2,7 @@ name = "tx3-registry-backend" version = "0.1.0" edition = "2021" +publish = false repository = "https://github.com/tx3-lang/registry" homepage = "https://registry.telchar.txpipe.io" documentation = "https://telchar.txpipe.io" diff --git a/backend/src/ast_to_svg.rs b/backend/src/ast_to_svg.rs index c0a38db..b400ad9 100644 --- a/backend/src/ast_to_svg.rs +++ b/backend/src/ast_to_svg.rs @@ -270,25 +270,29 @@ pub(crate) fn extract_party_from_expr(expr: &tir::Expression) -> Option fn sort_parties_by_connections(parties: &[Party], parameters: &[Parameter]) -> Vec { let mut party_indices: Vec = (0..parties.len()).collect(); - + // Create connections map: party_name -> [parameter_indices] - let mut connections_map: std::collections::HashMap> = std::collections::HashMap::new(); + let mut connections_map: std::collections::HashMap> = + std::collections::HashMap::new(); for (param_idx, param) in parameters.iter().enumerate() { if let Some(ref party_name) = param.party { - connections_map.entry(party_name.clone()).or_insert_with(Vec::new).push(param_idx); + connections_map + .entry(party_name.clone()) + .or_insert_with(Vec::new) + .push(param_idx); } } - + // Sort using an algorithm that minimizes line crossings optimize_party_order(&mut party_indices, parties, &connections_map); - + party_indices } fn optimize_party_order( - party_indices: &mut Vec, - parties: &[Party], - connections_map: &std::collections::HashMap> + party_indices: &mut Vec, + parties: &[Party], + connections_map: &std::collections::HashMap>, ) { // Simple bubble sort algorithm to minimize crossings by swapping adjacent parties let mut improved = true; @@ -307,30 +311,34 @@ fn would_reduce_crossings( party_indices: &[usize], swap_pos: usize, parties: &[Party], - connections_map: &std::collections::HashMap> + connections_map: &std::collections::HashMap>, ) -> bool { if swap_pos + 1 >= party_indices.len() { return false; } - + let party_a_idx = party_indices[swap_pos]; let party_b_idx = party_indices[swap_pos + 1]; - + let party_a = &parties[party_a_idx]; let party_b = &parties[party_b_idx]; - - let connections_a = connections_map.get(&party_a.name).map(|v| v.as_slice()).unwrap_or(&[]); - let connections_b = connections_map.get(&party_b.name).map(|v| v.as_slice()).unwrap_or(&[]); - + + let connections_a = connections_map + .get(&party_a.name) + .map(|v| v.as_slice()) + .unwrap_or(&[]); + let connections_b = connections_map + .get(&party_b.name) + .map(|v| v.as_slice()) + .unwrap_or(&[]); + // Calculate current crossings vs crossings after swap - let current_crossings = count_crossings_between_parties( - swap_pos, swap_pos + 1, connections_a, connections_b - ); - - let swapped_crossings = count_crossings_between_parties( - swap_pos + 1, swap_pos, connections_b, connections_a - ); - + let current_crossings = + count_crossings_between_parties(swap_pos, swap_pos + 1, connections_a, connections_b); + + let swapped_crossings = + count_crossings_between_parties(swap_pos + 1, swap_pos, connections_b, connections_a); + swapped_crossings < current_crossings } @@ -338,7 +346,7 @@ fn count_crossings_between_parties( party_a_pos: usize, party_b_pos: usize, connections_a: &[usize], - connections_b: &[usize] + connections_b: &[usize], ) -> usize { let mut crossings = 0; for &conn_a in connections_a { @@ -356,31 +364,34 @@ fn count_crossings_between_parties( crossings } - fn sort_parameters_by_connections(parameters: &[Parameter], parties: &[Party]) -> Vec { let mut param_indices: Vec = (0..parameters.len()).collect(); - + // Crear mapa de party_name -> posición en el array ordenado de parties let party_positions: std::collections::HashMap = parties .iter() .enumerate() .map(|(pos, party)| (party.name.clone(), pos)) .collect(); - + // Ordenar parámetros para que sigan el mismo orden que sus parties conectadas param_indices.sort_by(|&a, &b| { - let party_pos_a = parameters[a].party.as_ref() + let party_pos_a = parameters[a] + .party + .as_ref() .and_then(|name| party_positions.get(name)) .copied() .unwrap_or(usize::MAX); - let party_pos_b = parameters[b].party.as_ref() + let party_pos_b = parameters[b] + .party + .as_ref() .and_then(|name| party_positions.get(name)) .copied() .unwrap_or(usize::MAX); - + party_pos_a.cmp(&party_pos_b) }); - + param_indices } @@ -422,7 +433,11 @@ fn render_parameter(param: &Parameter, x: i32, y: i32, is_input: bool) -> String line_end = if is_input { "100%" } else { "80%" }, text_position = if is_input { "60%" } else { "40%" }, circle_cx = if is_input { "20%" } else { "80%" }, - color = if is_input { "rgba(81, 162, 255, 1)" } else { "rgba(255,0,127,1)" }, + color = if is_input { + "rgba(81, 162, 255, 1)" + } else { + "rgba(255,0,127,1)" + }, ) } @@ -465,7 +480,14 @@ pub fn tx_to_svg(ast: &Program, tx: &TxDef, params: Vec) -> String { let inputs = get_inputs(tx); let outputs = get_outputs(tx); - build_svg(&tx.name.value, ¶ms, input_parties, output_parties, inputs, outputs) + build_svg( + &tx.name.value, + ¶ms, + input_parties, + output_parties, + inputs, + outputs, + ) } pub fn tir_to_svg( @@ -479,7 +501,14 @@ pub fn tir_to_svg( let inputs = get_inputs_from_tir(tx); let outputs = get_outputs_from_tir(tx, output_names); - build_svg(name, ¶ms, input_parties, output_parties, inputs, outputs) + build_svg( + name, + ¶ms, + input_parties, + output_parties, + inputs, + outputs, + ) } fn build_svg( @@ -495,8 +524,14 @@ fn build_svg( let output_party_order = sort_parties_by_connections(&output_parties, &outputs); // Ahora ordenar parámetros basándose en el orden optimizado de parties - let ordered_input_parties: Vec = input_party_order.iter().map(|&i| input_parties[i].clone()).collect(); - let ordered_output_parties: Vec = output_party_order.iter().map(|&i| output_parties[i].clone()).collect(); + let ordered_input_parties: Vec = input_party_order + .iter() + .map(|&i| input_parties[i].clone()) + .collect(); + let ordered_output_parties: Vec = output_party_order + .iter() + .map(|&i| output_parties[i].clone()) + .collect(); let input_param_order = sort_parameters_by_connections(&inputs, &ordered_input_parties); let output_param_order = sort_parameters_by_connections(&outputs, &ordered_output_parties); @@ -536,7 +571,10 @@ fn build_svg( if let Some(ref name) = input.party { if let Some(original_party_idx) = input_parties.iter().position(|p| &p.name == name) { // Encontrar la posición de renderizado de esta party - if let Some(party_render_pos) = input_party_order.iter().position(|&idx| idx == original_party_idx) { + if let Some(party_render_pos) = input_party_order + .iter() + .position(|&idx| idx == original_party_idx) + { write!( svg, "", @@ -556,7 +594,10 @@ fn build_svg( if let Some(ref name) = output.party { if let Some(original_party_idx) = output_parties.iter().position(|p| &p.name == name) { // Encontrar la posición de renderizado de esta party - if let Some(party_render_pos) = output_party_order.iter().position(|&idx| idx == original_party_idx) { + if let Some(party_render_pos) = output_party_order + .iter() + .position(|&idx| idx == original_party_idx) + { write!( svg, "", diff --git a/backend/src/cache.rs b/backend/src/cache.rs index 2c2ee76..36222d3 100644 --- a/backend/src/cache.rs +++ b/backend/src/cache.rs @@ -96,14 +96,18 @@ impl DiskCache { /// Store `bytes` under `key`. No-op when disabled; errors are swallowed (the /// cache is best-effort and must never fail a request). pub async fn put(&self, key: &str, bytes: Vec) { - let Some(inner) = self.inner.clone() else { return }; + let Some(inner) = self.inner.clone() else { + return; + }; let key = key.to_string(); let _ = rocket::tokio::task::spawn_blocking(move || inner.put_sync(&key, &bytes)).await; } /// Remember that `key` is not found, with the short negative TTL. pub async fn put_negative(&self, key: &str) { - let Some(inner) = self.inner.clone() else { return }; + let Some(inner) = self.inner.clone() else { + return; + }; let key = key.to_string(); let _ = rocket::tokio::task::spawn_blocking(move || inner.put_negative_sync(&key)).await; } @@ -213,8 +217,12 @@ impl Inner { /// True when `path` exists and was modified within `ttl`. Any error (missing /// file, clock skew) is treated as not-fresh. fn fresh(path: &Path, ttl: Duration) -> bool { - let Ok(meta) = std::fs::metadata(path) else { return false }; - let Ok(modified) = meta.modified() else { return false }; + let Ok(meta) = std::fs::metadata(path) else { + return false; + }; + let Ok(modified) = meta.modified() else { + return false; + }; SystemTime::now() .duration_since(modified) .map(|age| age < ttl) @@ -228,7 +236,9 @@ fn with_suffix(path: &Path, suffix: &str) -> PathBuf { } fn collect_files(dir: &Path, out: &mut Vec<(PathBuf, SystemTime, u64)>) { - let Ok(entries) = std::fs::read_dir(dir) else { return }; + let Ok(entries) = std::fs::read_dir(dir) else { + return; + }; for entry in entries.flatten() { let path = entry.path(); let Ok(meta) = entry.metadata() else { continue }; @@ -277,7 +287,8 @@ mod tests { } fn tmp_dir(tag: &str) -> PathBuf { - let dir = std::env::temp_dir().join(format!("tx3-cache-test-{}-{}", tag, uuid::Uuid::new_v4())); + let dir = + std::env::temp_dir().join(format!("tx3-cache-test-{}-{}", tag, uuid::Uuid::new_v4())); std::fs::create_dir_all(&dir).unwrap(); dir } @@ -285,9 +296,16 @@ mod tests { #[test] fn put_get_round_trip() { let dir = tmp_dir("roundtrip"); - let inner = inner_in(&dir, Duration::from_secs(60), Duration::from_secs(60), 1 << 30); + let inner = inner_in( + &dir, + Duration::from_secs(60), + Duration::from_secs(60), + 1 << 30, + ); - inner.put_sync("og/v1/acme/widget/1.0.0", b"PNGDATA").unwrap(); + inner + .put_sync("og/v1/acme/widget/1.0.0", b"PNGDATA") + .unwrap(); match inner.get_sync("og/v1/acme/widget/1.0.0") { Some(Cached::Bytes(b)) => assert_eq!(b, b"PNGDATA"), _ => panic!("expected positive hit"), @@ -300,7 +318,12 @@ mod tests { #[test] fn positive_expires_past_ttl() { let dir = tmp_dir("ttl"); - let inner = inner_in(&dir, Duration::from_millis(1), Duration::from_secs(60), 1 << 30); + let inner = inner_in( + &dir, + Duration::from_millis(1), + Duration::from_secs(60), + 1 << 30, + ); inner.put_sync("logo/acme/widget/1.0.0", b"x").unwrap(); std::thread::sleep(Duration::from_millis(10)); @@ -312,10 +335,18 @@ mod tests { #[test] fn negative_sentinel_is_cached() { let dir = tmp_dir("neg"); - let inner = inner_in(&dir, Duration::from_secs(60), Duration::from_secs(60), 1 << 30); + let inner = inner_in( + &dir, + Duration::from_secs(60), + Duration::from_secs(60), + 1 << 30, + ); inner.put_negative_sync("nf/acme/ghost").unwrap(); - assert!(matches!(inner.get_sync("nf/acme/ghost"), Some(Cached::Negative))); + assert!(matches!( + inner.get_sync("nf/acme/ghost"), + Some(Cached::Negative) + )); std::fs::remove_dir_all(&dir).ok(); } @@ -323,7 +354,12 @@ mod tests { #[test] fn negative_expires_on_short_ttl() { let dir = tmp_dir("neg-ttl"); - let inner = inner_in(&dir, Duration::from_secs(60), Duration::from_millis(1), 1 << 30); + let inner = inner_in( + &dir, + Duration::from_secs(60), + Duration::from_millis(1), + 1 << 30, + ); inner.put_negative_sync("nf/acme/ghost").unwrap(); std::thread::sleep(Duration::from_millis(10)); @@ -339,7 +375,9 @@ mod tests { let inner = inner_in(&dir, Duration::from_secs(60), Duration::from_secs(60), 30); for i in 0..10 { - inner.put_sync(&format!("og/v1/acme/p{i}"), &[b'a'; 10]).unwrap(); + inner + .put_sync(&format!("og/v1/acme/p{i}"), &[b'a'; 10]) + .unwrap(); std::thread::sleep(Duration::from_millis(5)); // distinct mtimes } @@ -348,7 +386,9 @@ mod tests { let total: u64 = files.iter().map(|(_, _, len)| *len).sum(); assert!(total <= 30, "cache exceeded budget: {total}"); assert!( - files.iter().all(|(p, _, _)| !p.to_string_lossy().ends_with(TMP_SUFFIX)), + files + .iter() + .all(|(p, _, _)| !p.to_string_lossy().ends_with(TMP_SUFFIX)), "left a stray temp file" ); diff --git a/backend/src/db.rs b/backend/src/db.rs index bf3c6b0..6f14ed2 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -29,8 +29,7 @@ pub struct MatchRow { pub matched_at: chrono::DateTime, } -const SELECT_COLS: &str = - "id, tx_hash, block_slot, block_hash, source_name, protocol_name, \ +const SELECT_COLS: &str = "id, tx_hash, block_slot, block_hash, source_name, protocol_name, \ profile_name, tx_name, repo_scope, repo_name, repo_version, \ lifted::text AS lifted, matched_at"; diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 1b09203..56c1513 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,6 +1,6 @@ +pub mod ast_to_svg; pub mod cache; pub mod db; -pub mod schema; pub mod oci; -pub mod ast_to_svg; pub mod og_card; +pub mod schema; diff --git a/backend/src/main.rs b/backend/src/main.rs index c61e097..513cde1 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -8,7 +8,10 @@ use rocket::{ }; use rocket_cors::{AllowedHeaders, AllowedOrigins}; -use tx3_registry_backend::{cache::{Cached, DiskCache}, db, oci, og_card, schema}; +use tx3_registry_backend::{ + cache::{Cached, DiskCache}, + db, oci, og_card, schema, +}; #[macro_use] extern crate rocket; @@ -19,7 +22,10 @@ fn index<'a>() -> &'a str { } #[post("/graphql", data = "", format = "application/json")] -async fn graphql_request(schema: &State, req: GraphQLRequest) -> GraphQLResponse { +async fn graphql_request( + schema: &State, + req: GraphQLRequest, +) -> GraphQLResponse { req.execute(schema.inner()).await } @@ -152,8 +158,7 @@ async fn protocol_og_card( async fn rocket() -> _ { let _ = dotenv(); - let database_url = std::env::var("DATABASE_URL") - .unwrap_or_default(); + let database_url = std::env::var("DATABASE_URL").unwrap_or_default(); if database_url.is_empty() { panic!("DATABASE_URL is required but was not set or is empty"); } @@ -164,12 +169,16 @@ async fn rocket() -> _ { let cors = rocket_cors::CorsOptions { allowed_origins: AllowedOrigins::All, - allowed_methods: vec![Method::Get, Method::Post, Method::Options].into_iter().map(From::from).collect(), + allowed_methods: vec![Method::Get, Method::Post, Method::Options] + .into_iter() + .map(From::from) + .collect(), allowed_headers: AllowedHeaders::some(&["Authorization", "Accept"]), allow_credentials: true, ..Default::default() } - .to_cors().unwrap(); + .to_cors() + .unwrap(); let schema = schema::build_schema(pool.clone()); @@ -179,6 +188,15 @@ async fn rocket() -> _ { .manage(pool) .manage(schema) .manage(cache) - .mount("/", routes![index, graphql, graphql_request, protocol_logo, protocol_og_card]) + .mount( + "/", + routes![ + index, + graphql, + graphql_request, + protocol_logo, + protocol_og_card + ], + ) .attach(cors) -} \ No newline at end of file +} diff --git a/backend/src/oci.rs b/backend/src/oci.rs index a2beaf7..c404028 100644 --- a/backend/src/oci.rs +++ b/backend/src/oci.rs @@ -89,7 +89,7 @@ pub struct ProtocolJson { pub fn get_registry_api_url() -> String { let registry_host = std::env::var("REGISTRY_HOST").unwrap_or_default(); let registry_protocol = std::env::var("REGISTRY_PROTOCOL").unwrap_or_default(); - + return format!("{}://{}/v2", registry_protocol, registry_host); } @@ -110,17 +110,27 @@ fn get_client() -> Client { /// Resolve the newest tag for a `/` repo via the zot search API. /// Returns `None` when the repo does not exist or has no images. -pub async fn newest_tag(repo: &str) -> Result, Box> { +pub async fn newest_tag( + repo: &str, +) -> Result, Box> { let registry_api = get_registry_api_url(); let query = format!( r#"query ExpandedRepoInfo {{ ExpandedRepoInfo(repo: "{}") {{ Summary {{ Name NewestImage {{ Tag }} }} Images {{ Tag }} }} }}"#, repo ); - let url = format!("{}/_zot/ext/search?query={}", registry_api, urlencoding::encode(&query)); + let url = format!( + "{}/_zot/ext/search?query={}", + registry_api, + urlencoding::encode(&query) + ); let response = reqwest::get(&url).await?.json::().await?; - let Some(data) = response.data else { return Ok(None) }; - let Some(info) = data.expanded_repo_info else { return Ok(None) }; + let Some(data) = response.data else { + return Ok(None); + }; + let Some(info) = data.expanded_repo_info else { + return Ok(None); + }; if let Some(summary) = info.summary { if let Some(image) = summary.newest_image { @@ -137,23 +147,36 @@ pub async fn newest_tag(repo: &str) -> Result, Box Result> { +pub async fn get_oci_image( + repo: &str, + tag: &str, +) -> Result> { let registry_host = std::env::var("REGISTRY_HOST").unwrap_or_default(); let reference = Reference::try_from(format!("{}/{}:{}", registry_host, repo, tag))?; let client = get_client(); let auth = RegistryAuth::Anonymous; - let content = client.pull( - &reference, - &auth, - vec![MARKDOWN_MEDIA_TYPE, PROTOCOL_MEDIA_TYPE, TII_MEDIA_TYPE, LOGO_PNG_MEDIA_TYPE] - ).await?; + let content = client + .pull( + &reference, + &auth, + vec![ + MARKDOWN_MEDIA_TYPE, + PROTOCOL_MEDIA_TYPE, + TII_MEDIA_TYPE, + LOGO_PNG_MEDIA_TYPE, + ], + ) + .await?; Ok(content) } pub fn get_readme(image: &ImageData) -> Option { - let readme = image.layers.iter().find(|l| l.media_type == MARKDOWN_MEDIA_TYPE); + let readme = image + .layers + .iter() + .find(|l| l.media_type == MARKDOWN_MEDIA_TYPE); if let Some(readme) = readme { return Some(String::from_utf8_lossy(&readme.data).to_string()); @@ -163,7 +186,10 @@ pub fn get_readme(image: &ImageData) -> Option { } pub fn get_protocol(image: &ImageData) -> Option { - let protocol = image.layers.iter().find(|l| l.media_type == PROTOCOL_MEDIA_TYPE); + let protocol = image + .layers + .iter() + .find(|l| l.media_type == PROTOCOL_MEDIA_TYPE); if let Some(protocol) = protocol { return Some(String::from_utf8_lossy(&protocol.data).to_string()); @@ -188,4 +214,4 @@ pub fn get_tii(image: &ImageData) -> Option { } return None; -} \ No newline at end of file +} diff --git a/backend/src/og_card.rs b/backend/src/og_card.rs index c358584..f5de84d 100644 --- a/backend/src/og_card.rs +++ b/backend/src/og_card.rs @@ -76,14 +76,24 @@ impl std::error::Error for RenderError {} pub fn render_card(data: &CardData) -> Result, RenderError> { let svg = build_card_svg(data); - let opt = usvg::Options { fontdb: fontdb(), ..Default::default() }; + let opt = usvg::Options { + fontdb: fontdb(), + ..Default::default() + }; let tree = usvg::Tree::from_str(&svg, &opt).map_err(RenderError::Parse)?; - let mut pixmap = tiny_skia::Pixmap::new(WIDTH as u32, HEIGHT as u32).ok_or(RenderError::Pixmap)?; - resvg::render(&tree, tiny_skia::Transform::identity(), &mut pixmap.as_mut()); + let mut pixmap = + tiny_skia::Pixmap::new(WIDTH as u32, HEIGHT as u32).ok_or(RenderError::Pixmap)?; + resvg::render( + &tree, + tiny_skia::Transform::identity(), + &mut pixmap.as_mut(), + ); - pixmap.encode_png().map_err(|e| RenderError::Encode(e.to_string())) + pixmap + .encode_png() + .map_err(|e| RenderError::Encode(e.to_string())) } /// Shared, lazily-built font database (Inter Regular + SemiBold). @@ -111,8 +121,14 @@ fn build_card_svg(data: &CardData) -> String { ); // Background + subtle accent rule along the top. - let _ = write!(s, r#""#); - let _ = write!(s, r#""#); + let _ = write!( + s, + r#""# + ); + let _ = write!( + s, + r#""# + ); // Header: logo tile with the protocol name on the first line and the // scope + version on the second line beside it. @@ -143,7 +159,12 @@ fn build_card_svg(data: &CardData) -> String { ); // Description, wrapped to at most two lines. - if let Some(desc) = data.description.as_deref().map(str::trim).filter(|d| !d.is_empty()) { + if let Some(desc) = data + .description + .as_deref() + .map(str::trim) + .filter(|d| !d.is_empty()) + { let lines = wrap_to_lines(desc, CONTENT_W, 34.0, 2); let mut dy = 300.0; for line in lines { @@ -297,7 +318,8 @@ fn text_width(text: &str, font_size: f32, bold: bool) -> f32 { let mut units = 0.0f32; for c in text.chars() { units += match c { - 'i' | 'l' | 'j' | 'I' | '.' | ',' | '\'' | '|' | '!' | ':' | ';' | '(' | ')' | '[' | ']' => 0.30, + 'i' | 'l' | 'j' | 'I' | '.' | ',' | '\'' | '|' | '!' | ':' | ';' | '(' | ')' | '[' + | ']' => 0.30, 'f' | 't' | 'r' | ' ' => 0.34, 'm' | 'M' | 'w' | 'W' => 0.88, 'A'..='Z' => 0.68, diff --git a/backend/src/schema/match_query/cursor.rs b/backend/src/schema/match_query/cursor.rs index b4b20cb..413f872 100644 --- a/backend/src/schema/match_query/cursor.rs +++ b/backend/src/schema/match_query/cursor.rs @@ -19,7 +19,9 @@ pub fn encode_cursor(id: i64) -> String { } pub fn decode_cursor(cursor: &str) -> Result { - let bytes = STANDARD.decode(cursor).map_err(|_| CursorError::Malformed)?; + let bytes = STANDARD + .decode(cursor) + .map_err(|_| CursorError::Malformed)?; let s = std::str::from_utf8(&bytes).map_err(|_| CursorError::Malformed)?; let suffix = s.strip_prefix("id:").ok_or(CursorError::Malformed)?; let id: i64 = suffix.parse().map_err(|_| CursorError::Malformed)?; diff --git a/backend/src/schema/match_query/mod.rs b/backend/src/schema/match_query/mod.rs index 5d08ee5..a184d62 100644 --- a/backend/src/schema/match_query/mod.rs +++ b/backend/src/schema/match_query/mod.rs @@ -101,7 +101,9 @@ mod tests { repo_name: "orcfax-burn".to_string(), repo_version: "1.0.0".to_string(), lifted: "{}".to_string(), - matched_at: chrono::Utc.with_ymd_and_hms(2026, 5, 14, 10, 23, 45).unwrap(), + matched_at: chrono::Utc + .with_ymd_and_hms(2026, 5, 14, 10, 23, 45) + .unwrap(), } } diff --git a/backend/src/schema/match_query/query.rs b/backend/src/schema/match_query/query.rs index a4d9f44..669adbc 100644 --- a/backend/src/schema/match_query/query.rs +++ b/backend/src/schema/match_query/query.rs @@ -1,4 +1,7 @@ -use async_graphql::{connection::{CursorType, Edge}, Context, Error, Object}; +use async_graphql::{ + connection::{CursorType, Edge}, + Context, Error, Object, +}; use crate::db; @@ -54,24 +57,27 @@ impl MatchQuery { let after_id: Option = match after.as_deref() { None => None, Some(cursor) => { - let mc = MatchCursor::decode_cursor(cursor) - .map_err(|_| Error::new("invalid cursor"))?; + let mc = + MatchCursor::decode_cursor(cursor).map_err(|_| Error::new("invalid cursor"))?; Some(mc.0) } }; let pool = ctx.data_unchecked::(); - let (rows, has_next) = db::fetch_matches(pool, &scope, &name, version.as_deref(), first_i64, after_id) - .await - .map_err(|e| { - eprintln!("database error in protocol_matches: {e}"); - Error::new("database unavailable") - })?; + let (rows, has_next) = + db::fetch_matches(pool, &scope, &name, version.as_deref(), first_i64, after_id) + .await + .map_err(|e| { + eprintln!("database error in protocol_matches: {e}"); + Error::new("database unavailable") + })?; let mut connection = MatchConnection::new(after.is_some(), has_next); for row in rows { - connection.edges.push(Edge::new(MatchCursor(row.id), Match::from(row))); + connection + .edges + .push(Edge::new(MatchCursor(row.id), Match::from(row))); } Ok(connection) diff --git a/backend/src/schema/mod.rs b/backend/src/schema/mod.rs index 1d3e250..7c8dcf8 100644 --- a/backend/src/schema/mod.rs +++ b/backend/src/schema/mod.rs @@ -3,9 +3,9 @@ use std::{fs::File, io::Write}; use async_graphql::{EmptyMutation, EmptySubscription, MergedObject, Schema}; use sqlx::PgPool; -pub mod protocol; -pub mod pagination; mod match_query; +pub mod pagination; +pub mod protocol; pub use match_query::{Match, MatchConnection, MatchCursor}; @@ -24,9 +24,10 @@ pub fn build_schema(pool: PgPool) -> Tx3Schema { let sdl = schema.sdl(); let mut file = File::create("schema.graphql").expect("Failed to create schema file"); - file.write_all(sdl.as_bytes()).expect("Failed to write schema"); + file.write_all(sdl.as_bytes()) + .expect("Failed to write schema"); - return schema + return schema; } #[cfg(test)] @@ -37,9 +38,16 @@ mod tests { fn regenerate_sdl() { let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); let sdl = schema.sdl(); - assert!(sdl.contains("protocolMatches"), "SDL must contain protocolMatches"); - assert!(sdl.contains("protocolMatch("), "SDL must contain protocolMatch"); + assert!( + sdl.contains("protocolMatches"), + "SDL must contain protocolMatches" + ); + assert!( + sdl.contains("protocolMatch("), + "SDL must contain protocolMatch" + ); let mut file = File::create("schema.graphql").expect("Failed to create schema file"); - file.write_all(sdl.as_bytes()).expect("Failed to write schema"); + file.write_all(sdl.as_bytes()) + .expect("Failed to write schema"); } } diff --git a/backend/src/schema/pagination/mod.rs b/backend/src/schema/pagination/mod.rs index f84af6c..0558bbd 100644 --- a/backend/src/schema/pagination/mod.rs +++ b/backend/src/schema/pagination/mod.rs @@ -3,23 +3,26 @@ use async_graphql::SimpleObject; #[derive(SimpleObject)] struct PaginationInfo { - total_nodes: usize, - page_size: usize, + total_nodes: usize, + page_size: usize, } #[derive(SimpleObject)] pub struct AdditionalInfo { - metadata: Option, + metadata: Option, } impl AdditionalInfo { - pub fn new(total_nodes: usize, page_size: usize) -> Self { - Self { - metadata: Some(PaginationInfo { total_nodes, page_size }) + pub fn new(total_nodes: usize, page_size: usize) -> Self { + Self { + metadata: Some(PaginationInfo { + total_nodes, + page_size, + }), + } } - } - pub fn empty() -> Self { - Self { metadata: None } - } + pub fn empty() -> Self { + Self { metadata: None } + } } diff --git a/backend/src/schema/protocol/mod.rs b/backend/src/schema/protocol/mod.rs index ba20e19..3da0db2 100644 --- a/backend/src/schema/protocol/mod.rs +++ b/backend/src/schema/protocol/mod.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; -use std::fmt; use async_graphql::{ComplexObject, Enum, SimpleObject, ID}; use serde::Deserialize; +use std::collections::HashMap; +use std::fmt; mod query; pub use query::{build_protocol, load_protocol, resolve_protocol, ProtocolQuery, ResolvedProtocol}; @@ -153,10 +153,14 @@ impl Protocol { async fn parties(&self) -> Vec { let Some(tii) = &self.tii else { return vec![] }; - let mut parties: Vec = tii.parties.iter().map(|(name, party)| Party { - name: name.clone(), - description: party.description.clone(), - }).collect(); + let mut parties: Vec = tii + .parties + .iter() + .map(|(name, party)| Party { + name: name.clone(), + description: party.description.clone(), + }) + .collect(); parties.sort_by(|a, b| a.name.cmp(&b.name)); parties @@ -167,19 +171,27 @@ impl Protocol { const KNOWN_ORDER: &[&str] = &["local", "preview", "preprod", "mainnet"]; - let mut profiles: Vec = tii.profiles.iter().map(|(name, profile)| Profile { - name: name.clone(), - description: profile.description.clone(), - environment: if profile.environment.is_null() { - None - } else { - Some(profile.environment.to_string()) - }, - parties: profile.parties.iter().map(|(name, address)| ProfileParty { + let mut profiles: Vec = tii + .profiles + .iter() + .map(|(name, profile)| Profile { name: name.clone(), - address: address.clone(), - }).collect(), - }).collect(); + description: profile.description.clone(), + environment: if profile.environment.is_null() { + None + } else { + Some(profile.environment.to_string()) + }, + parties: profile + .parties + .iter() + .map(|(name, address)| ProfileParty { + name: name.clone(), + address: address.clone(), + }) + .collect(), + }) + .collect(); profiles.sort_by(|a, b| { let pos_a = KNOWN_ORDER.iter().position(|&k| k == a.name); @@ -198,31 +210,41 @@ impl Protocol { async fn environment(&self) -> Vec { let Some(tii) = &self.tii else { return vec![] }; - let Some(env) = &tii.environment else { return vec![] }; - let Some(props) = env.get("properties").and_then(|p| p.as_object()) else { return vec![] }; - - props.iter().map(|(name, schema)| { - let r#type = schema.get("type") - .and_then(|t| t.as_str()) - .map(String::from) - .or_else(|| { - schema.get("$ref") - .and_then(|r| r.as_str()) - .and_then(|r| r.rsplit_once('#')) - .map(|(_, fragment)| fragment.to_string()) - }) - .unwrap_or_else(|| "unknown".to_string()); - - let description = schema.get("description") - .and_then(|d| d.as_str()) - .map(String::from); - - EnvironmentParam { - name: name.clone(), - description, - r#type, - } - }).collect() + let Some(env) = &tii.environment else { + return vec![]; + }; + let Some(props) = env.get("properties").and_then(|p| p.as_object()) else { + return vec![]; + }; + + props + .iter() + .map(|(name, schema)| { + let r#type = schema + .get("type") + .and_then(|t| t.as_str()) + .map(String::from) + .or_else(|| { + schema + .get("$ref") + .and_then(|r| r.as_str()) + .and_then(|r| r.rsplit_once('#')) + .map(|(_, fragment)| fragment.to_string()) + }) + .unwrap_or_else(|| "unknown".to_string()); + + let description = schema + .get("description") + .and_then(|d| d.as_str()) + .map(String::from); + + EnvironmentParam { + name: name.clone(), + description, + r#type, + } + }) + .collect() } } @@ -314,7 +336,11 @@ impl Protocol { // Reference types encode the tx3 type name as the last path segment, // e.g. ".../tii#/$defs/Address" -> "Address". if let Some(reference) = schema.get("$ref").and_then(|r| r.as_str()) { - return reference.rsplit('/').next().unwrap_or(reference).to_string(); + return reference + .rsplit('/') + .next() + .unwrap_or(reference) + .to_string(); } match schema.get("type").and_then(|t| t.as_str()) { @@ -338,28 +364,32 @@ impl Protocol { } fn transactions_from_tii(&self, tii_file: &TiiFile) -> Vec { - tii_file.transactions.iter().map(|(name, tx)| { - // The TII params schema lists exactly the declared transaction - // params; prefer it over the lowered TIR, which also surfaces env - // vars and party references as required values. - let parameters = match tx.params.as_ref() { - Some(schema) => Self::params_from_schema(schema), - None => Self::extract_params_from_tir(&tx.tir), - }; - let inputs = Self::extract_inputs_from_tir(&tx.tir); - let outputs = Self::extract_outputs_from_tir(&tx.tir); - - Tx { - name: name.clone(), - description: tx.description.clone(), - parameters, - inputs, - outputs, - tir: tx.tir.content.clone(), - tir_version: tx.tir.version.clone(), - protocol_source: self.source.clone(), - } - }).collect() + tii_file + .transactions + .iter() + .map(|(name, tx)| { + // The TII params schema lists exactly the declared transaction + // params; prefer it over the lowered TIR, which also surfaces env + // vars and party references as required values. + let parameters = match tx.params.as_ref() { + Some(schema) => Self::params_from_schema(schema), + None => Self::extract_params_from_tir(&tx.tir), + }; + let inputs = Self::extract_inputs_from_tir(&tx.tir); + let outputs = Self::extract_outputs_from_tir(&tx.tir); + + Tx { + name: name.clone(), + description: tx.description.clone(), + parameters, + inputs, + outputs, + tir: tx.tir.content.clone(), + tir_version: tx.tir.version.clone(), + protocol_source: self.source.clone(), + } + }) + .collect() } fn decode_tir(tir: &TiiTirEnvelope) -> Option { @@ -369,7 +399,9 @@ impl Protocol { } fn extract_params_from_tir(tir: &TiiTirEnvelope) -> Vec { - let Some(any_tir) = Self::decode_tir(tir) else { return vec![] }; + let Some(any_tir) = Self::decode_tir(tir) else { + return vec![]; + }; let mut parameters: Vec = any_tir .params() @@ -386,33 +418,43 @@ impl Protocol { } fn extract_inputs_from_tir(tir: &TiiTirEnvelope) -> Vec { - let Some(tx3_tir::encoding::AnyTir::V1Beta0(tx)) = Self::decode_tir(tir) else { return vec![] }; - - tx.inputs.iter().map(|input| { - let party = ast_to_svg::extract_party_from_expr(&input.utxos); - let has_redeemer = !input.redeemer.is_none(); + let Some(tx3_tir::encoding::AnyTir::V1Beta0(tx)) = Self::decode_tir(tir) else { + return vec![]; + }; - TxInput { - name: input.name.clone(), - party, - has_redeemer, - } - }).collect() + tx.inputs + .iter() + .map(|input| { + let party = ast_to_svg::extract_party_from_expr(&input.utxos); + let has_redeemer = !input.redeemer.is_none(); + + TxInput { + name: input.name.clone(), + party, + has_redeemer, + } + }) + .collect() } fn extract_outputs_from_tir(tir: &TiiTirEnvelope) -> Vec { - let Some(tx3_tir::encoding::AnyTir::V1Beta0(tx)) = Self::decode_tir(tir) else { return vec![] }; - - tx.outputs.iter().map(|output| { - let party = ast_to_svg::extract_party_from_expr(&output.address); - let has_datum = !output.datum.is_none(); + let Some(tx3_tir::encoding::AnyTir::V1Beta0(tx)) = Self::decode_tir(tir) else { + return vec![]; + }; - TxOutput { - party, - has_datum, - optional: output.optional, - } - }).collect() + tx.outputs + .iter() + .map(|output| { + let party = ast_to_svg::extract_party_from_expr(&output.address); + let has_datum = !output.datum.is_none(); + + TxOutput { + party, + has_datum, + optional: output.optional, + } + }) + .collect() } fn transactions_from_source(&self) -> Vec { @@ -421,29 +463,33 @@ impl Protocol { None => return vec![], }; - protocol.txs.iter().map(|tx| { - let parameters = Self::extract_params(&protocol, &tx.name.value); - let tx_tir = tx3_lang::lowering::lower(&protocol, &tx.name.value).unwrap(); - let (tx_bytes, version) = tx3_tir::encoding::to_bytes(&tx_tir); - - let tir_envelope = TiiTirEnvelope { - content: hex::encode(&tx_bytes), - version: version.to_string(), - }; - let inputs = Self::extract_inputs_from_tir(&tir_envelope); - let outputs = Self::extract_outputs_from_tir(&tir_envelope); - - Tx { - name: tx.name.value.clone(), - description: None, - parameters, - inputs, - outputs, - tir: tir_envelope.content, - tir_version: tir_envelope.version, - protocol_source: self.source.clone(), - } - }).collect() + protocol + .txs + .iter() + .map(|tx| { + let parameters = Self::extract_params(&protocol, &tx.name.value); + let tx_tir = tx3_lang::lowering::lower(&protocol, &tx.name.value).unwrap(); + let (tx_bytes, version) = tx3_tir::encoding::to_bytes(&tx_tir); + + let tir_envelope = TiiTirEnvelope { + content: hex::encode(&tx_bytes), + version: version.to_string(), + }; + let inputs = Self::extract_inputs_from_tir(&tir_envelope); + let outputs = Self::extract_outputs_from_tir(&tir_envelope); + + Tx { + name: tx.name.value.clone(), + description: None, + parameters, + inputs, + outputs, + tir: tir_envelope.content, + tir_version: tir_envelope.version, + protocol_source: self.source.clone(), + } + }) + .collect() } } @@ -473,7 +519,12 @@ impl Tx { version: self.tir_version.clone(), }; if let Some(tx3_tir::encoding::AnyTir::V1Beta0(tx)) = Protocol::decode_tir(&tir_envelope) { - return Some(ast_to_svg::tir_to_svg(&self.name, &tx, param_names, &output_names)); + return Some(ast_to_svg::tir_to_svg( + &self.name, + &tx, + param_names, + &output_names, + )); } let source = self.protocol_source.as_ref()?; @@ -502,4 +553,4 @@ impl fmt::Display for ProtocolSort { ProtocolSort::Downloads => write!(f, "DOWNLOADS"), } } -} \ No newline at end of file +} diff --git a/backend/src/schema/protocol/query.rs b/backend/src/schema/protocol/query.rs index 8c262b2..59c8bf6 100644 --- a/backend/src/schema/protocol/query.rs +++ b/backend/src/schema/protocol/query.rs @@ -2,8 +2,8 @@ use async_graphql::{connection::Edge, types::connection::Connection, Context, Er use oci_client::client::ImageData; use urlencoding::encode; -use crate::{oci, schema::pagination::AdditionalInfo}; use super::{Protocol, ProtocolSort, TiiFile}; +use crate::{oci, schema::pagination::AdditionalInfo}; /// A protocol's newest image resolved from the registry search, *before* the /// (heavy) OCI pull. Carries everything needed to build a cache key and, on a @@ -37,7 +37,8 @@ pub async fn resolve_protocol(scope: &str, name: &str) -> Result Result Result Result<(Protocol, Ima let readme = oci::get_readme(&oci_image); let source = oci::get_protocol(&oci_image); - let tii = oci::get_tii(&oci_image) - .and_then(|json| serde_json::from_str::(&json).ok()); + let tii = oci::get_tii(&oci_image).and_then(|json| serde_json::from_str::(&json).ok()); // The project homepage travels as the standard OCI `url` annotation on the // published manifest (`trix publish` maps `[protocol].homepage` to it). @@ -99,7 +113,9 @@ pub async fn build_protocol(resolved: ResolvedProtocol) -> Result<(Protocol, Ima chrono::DateTime::parse_from_rfc3339(&published_date) .unwrap() .timestamp() - } else { 0 }; + } else { + 0 + }; let protocol = Protocol { id: ID::from(id), @@ -128,7 +144,9 @@ pub async fn load_protocol( scope: &str, name: &str, ) -> Result, Error> { - let Some(resolved) = resolve_protocol(scope, name).await? else { return Ok(None) }; + let Some(resolved) = resolve_protocol(scope, name).await? else { + return Ok(None); + }; Ok(Some(build_protocol(resolved).await?)) } @@ -148,7 +166,8 @@ impl ProtocolQuery { let _offset = offset.unwrap_or(0); let _page_size = page_size.unwrap_or(15).min(30); let registry_api = oci::get_registry_api_url(); - let query_param = format!(r#" + let query_param = format!( + r#" query GlobalSearch {{ GlobalSearch(requestedPage: {{ limit: {}, offset: {}, sortBy: {} }}, query: "{}") {{ Page {{ TotalCount ItemCount }} @@ -158,8 +177,12 @@ impl ProtocolQuery { }} }} }} - "#, _page_size, _offset, sort_by.unwrap_or(ProtocolSort::AlphabeticAsc), search.unwrap_or_default()); - + "#, + _page_size, + _offset, + sort_by.unwrap_or(ProtocolSort::AlphabeticAsc), + search.unwrap_or_default() + ); let encode_query = encode(&query_param); let url = format!("{}/_zot/ext/search?query={}", registry_api, encode_query); @@ -167,25 +190,34 @@ impl ProtocolQuery { if response.error.is_some() { println!("error: {:?}", response.error); - return Ok(Connection::with_additional_fields(false, false, AdditionalInfo::empty())); + return Ok(Connection::with_additional_fields( + false, + false, + AdditionalInfo::empty(), + )); } if response.data.is_some() { let data = response.data.unwrap(); - + if data.global_search.is_some() { let info = data.global_search.unwrap(); let offset_usize = _offset as usize; - let page = info.page.unwrap_or(oci::PageInfo { total_count: 0, item_count: 0 }); + let page = info.page.unwrap_or(oci::PageInfo { + total_count: 0, + item_count: 0, + }); let mut connection = Connection::with_additional_fields( _offset > 0, (offset_usize + page.item_count as usize) < page.total_count as usize, AdditionalInfo::new(page.total_count as usize, page.item_count as usize), ); - + if let Some(repos) = info.repos { for (idx, repo) in repos.iter().enumerate() { - let Some(image) = repo.newest_image.clone() else { continue }; + let Some(image) = repo.newest_image.clone() else { + continue; + }; let mut source = None; if ctx.look_ahead().field("nodes").field("source").exists() { @@ -199,7 +231,9 @@ impl ProtocolQuery { chrono::DateTime::parse_from_rfc3339(&published_date) .unwrap() .timestamp() - } else { 0 }; + } else { + 0 + }; let protocol = Protocol { id: ID::from(repo.name.clone()), @@ -217,18 +251,26 @@ impl ProtocolQuery { tii: None, }; - connection.edges.push(Edge::new(offset_usize + idx, protocol)); + connection + .edges + .push(Edge::new(offset_usize + idx, protocol)); } } - - return Ok::<_, Error>(connection) + + return Ok::<_, Error>(connection); } } - return Ok(Connection::with_additional_fields(false, false, AdditionalInfo::empty())); + return Ok(Connection::with_additional_fields( + false, + false, + AdditionalInfo::empty(), + )); } async fn protocol(&self, scope: String, name: String) -> Result, Error> { - Ok(load_protocol(&scope, &name).await?.map(|(protocol, _)| protocol)) + Ok(load_protocol(&scope, &name) + .await? + .map(|(protocol, _)| protocol)) } } diff --git a/backend/tests/db_matches.rs b/backend/tests/db_matches.rs index 846f7f7..209a88a 100644 --- a/backend/tests/db_matches.rs +++ b/backend/tests/db_matches.rs @@ -54,7 +54,10 @@ async fn fetch_matches_orders_newest_first(pool: PgPool) { assert_eq!(rows.len(), 3); // ids should be in descending order for window in rows.windows(2) { - assert!(window[0].id > window[1].id, "rows should be ordered by id DESC"); + assert!( + window[0].id > window[1].id, + "rows should be ordered by id DESC" + ); } } @@ -64,10 +67,9 @@ async fn fetch_matches_filters_by_version(pool: PgPool) { insert_match(&pool, "txpipe", "orcfax-burn", "2.0.0", &[2u8; 32]).await; insert_match(&pool, "txpipe", "orcfax-burn", "2.0.0", &[3u8; 32]).await; - let (rows, has_next) = - fetch_matches(&pool, "txpipe", "orcfax-burn", Some("2.0.0"), 10, None) - .await - .expect("fetch_matches failed"); + let (rows, has_next) = fetch_matches(&pool, "txpipe", "orcfax-burn", Some("2.0.0"), 10, None) + .await + .expect("fetch_matches failed"); assert_eq!(rows.len(), 2); assert!(!has_next); @@ -90,10 +92,16 @@ async fn fetch_matches_paginates_with_after_id(pool: PgPool) { // after_id = id of the oldest row in page1 (last element since DESC order) let oldest_in_page1 = page1.last().unwrap().id; - let (page2, _) = - fetch_matches(&pool, "txpipe", "orcfax-burn", None, 2, Some(oldest_in_page1)) - .await - .expect("page2 fetch failed"); + let (page2, _) = fetch_matches( + &pool, + "txpipe", + "orcfax-burn", + None, + 2, + Some(oldest_in_page1), + ) + .await + .expect("page2 fetch failed"); assert_eq!(page2.len(), 2); // no overlapping ids @@ -111,7 +119,10 @@ async fn fetch_matches_paginates_with_after_id(pool: PgPool) { // natural order: every id in page1 > every id in page2 (newest first across pages) let min_p1 = ids_p1.iter().copied().min().unwrap(); let max_p2 = ids_p2.iter().copied().max().unwrap(); - assert!(min_p1 > max_p2, "page1 ids should all be newer than page2 ids"); + assert!( + min_p1 > max_p2, + "page1 ids should all be newer than page2 ids" + ); } #[sqlx::test(migrations = "../tracker/migrations")] diff --git a/backend/tests/graphql_protocol_matches.rs b/backend/tests/graphql_protocol_matches.rs index ea967fd..e3007de 100644 --- a/backend/tests/graphql_protocol_matches.rs +++ b/backend/tests/graphql_protocol_matches.rs @@ -85,7 +85,10 @@ async fn protocol_matches_pagination_chain(pool: PgPool) { assert!(res1.errors.is_empty(), "page1 errors: {:?}", res1.errors); let data1 = res1.data.into_json().unwrap(); - let nodes1 = data1["protocolMatches"]["nodes"].as_array().unwrap().clone(); + let nodes1 = data1["protocolMatches"]["nodes"] + .as_array() + .unwrap() + .clone(); assert_eq!(nodes1.len(), 2, "page1 should have 2 nodes"); let end_cursor = data1["protocolMatches"]["pageInfo"]["endCursor"] @@ -105,7 +108,10 @@ async fn protocol_matches_pagination_chain(pool: PgPool) { assert!(res2.errors.is_empty(), "page2 errors: {:?}", res2.errors); let data2 = res2.data.into_json().unwrap(); - let nodes2 = data2["protocolMatches"]["nodes"].as_array().unwrap().clone(); + let nodes2 = data2["protocolMatches"]["nodes"] + .as_array() + .unwrap() + .clone(); assert_eq!(nodes2.len(), 2, "page2 should have 2 nodes"); let has_next_page2 = data2["protocolMatches"]["pageInfo"]["hasNextPage"] @@ -122,7 +128,10 @@ async fn protocol_matches_pagination_chain(pool: PgPool) { .map(|n| n["id"].as_str().unwrap().to_string()) .collect(); - assert!(ids1.is_disjoint(&ids2), "pages should have no overlapping ids"); + assert!( + ids1.is_disjoint(&ids2), + "pages should have no overlapping ids" + ); let all_ids: std::collections::HashSet<_> = ids1.union(&ids2).collect(); assert_eq!(all_ids.len(), 4, "combined pages should cover all 4 rows"); @@ -172,7 +181,10 @@ async fn protocol_matches_clamps_first(pool: PgPool) { .as_array() .unwrap() .len(); - assert!(count <= 200, "clamped first should not return more than 200 rows"); + assert!( + count <= 200, + "clamped first should not return more than 200 rows" + ); } #[sqlx::test(migrations = "../tracker/migrations")] diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..9e9ad67 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,61 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +footer = """ + +""" +trim = true +postprocessors = [ + # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, +] + +[git] +conventional_commits = true +filter_unconventional = true +split_commits = false +commit_preprocessors = [ + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +commit_parsers = [ + { message = "^feat", group = "🚀 Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^ci", group = "🔧 Continuous Integration" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🚜 Refactor" }, + { message = "^style", group = "🎨 Styling" }, + { message = "^test", group = "🧪 Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, +] +protect_breaking_commits = false +filter_commits = false +topo_order = false +sort_commits = "oldest" +# limit_commits = 42 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ff79a41 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.91" +components = ["rustfmt", "clippy"] diff --git a/tracker/Cargo.toml b/tracker/Cargo.toml index b1e0663..86a683a 100644 --- a/tracker/Cargo.toml +++ b/tracker/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" license = "Apache-2.0" repository = "https://github.com/tx3-lang/registry" description = "24/7 tracker daemon for the tx3 registry — lifts matched txs into Postgres" +publish = false [lib] name = "tx3_registry_tracker" diff --git a/tracker/src/discovery.rs b/tracker/src/discovery.rs index 6ffd528..16e7155 100644 --- a/tracker/src/discovery.rs +++ b/tracker/src/discovery.rs @@ -143,10 +143,7 @@ fn registry_host(registry_url: &str) -> &str { /// Loops in steps of `LIMIT` until `Page.ItemCount < LIMIT`. Returns the /// assembled list of `RepoSummary` values (tag-less entries are skipped with a /// warning). -async fn query_repo_list( - http: &reqwest::Client, - base_url: &str, -) -> Result> { +async fn query_repo_list(http: &reqwest::Client, base_url: &str) -> Result> { let mut repos: Vec = Vec::new(); let mut offset: i64 = 0; @@ -182,9 +179,7 @@ async fn query_repo_list( .unwrap() .repo_list_with_newest_image .ok_or_else(|| { - Error::Config( - "zot response missing RepoListWithNewestImage field".to_string(), - ) + Error::Config("zot response missing RepoListWithNewestImage field".to_string()) })?; let item_count = page_data.page.item_count; @@ -197,7 +192,8 @@ async fn query_repo_list( continue; } }; - let scope = wire.name + let scope = wire + .name .split_once('/') .map(|(s, _)| s.to_string()) .unwrap_or_default(); @@ -227,9 +223,8 @@ async fn pull_tii( name: &str, version: &str, ) -> Result { - let reference = - Reference::try_from(format!("{registry_host_str}/{scope}/{name}:{version}")) - .map_err(|e| Error::Config(format!("invalid OCI reference: {e}")))?; + let reference = Reference::try_from(format!("{registry_host_str}/{scope}/{name}:{version}")) + .map_err(|e| Error::Config(format!("invalid OCI reference: {e}")))?; // Fetch the manifest, then pull only the `application/tii+json` layer's // blob by digest. `oci-client`'s high-level `pull` validates *every* layer @@ -273,7 +268,10 @@ async fn pull_tii( /// /// Errors if the registry is unreachable, the catalog is empty, every /// protocol is filtered out, or any individual protocol fails to pull/decode. -pub async fn fetch_catalog(oci: &OciConfig, default_profile: &str) -> Result> { +pub async fn fetch_catalog( + oci: &OciConfig, + default_profile: &str, +) -> Result> { let http = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() @@ -353,8 +351,7 @@ pub async fn fetch_catalog(oci: &OciConfig, default_profile: &str) -> Result, oci: &OciConfig) -> Vec { - let include_all = - oci.include_scopes.is_empty() && oci.include_names.is_empty(); + let include_all = oci.include_scopes.is_empty() && oci.include_names.is_empty(); repos .into_iter() diff --git a/tracker/src/main.rs b/tracker/src/main.rs index 1e00dbe..33696c2 100644 --- a/tracker/src/main.rs +++ b/tracker/src/main.rs @@ -17,6 +17,7 @@ async fn main() { } fn init_tracing() { - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("tracker=info,tx3_registry_tracker=info")); + let filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("tracker=info,tx3_registry_tracker=info")); tracing_subscriber::fmt().with_env_filter(filter).init(); } diff --git a/tracker/src/process.rs b/tracker/src/process.rs index 22af776..5e31504 100644 --- a/tracker/src/process.rs +++ b/tracker/src/process.rs @@ -309,10 +309,7 @@ fn select_matches(candidates: Vec>, mode: MatchMode) -> Vec< /// pull the resolved-output CBOR from `as_output.original_cbor`. This is what /// the v1beta spec carries for free, removing the need for a follow-up /// ReadUtxos round-trip (which can't return spent inputs anyway). -fn collect_resolved_inputs( - tx: &u5c_cardano::Tx, - era: Era, -) -> BTreeMap { +fn collect_resolved_inputs(tx: &u5c_cardano::Tx, era: Era) -> BTreeMap { let mut out = BTreeMap::new(); let all_inputs = tx.inputs.iter().chain(tx.reference_inputs.iter()); for input in all_inputs { diff --git a/tracker/src/store.rs b/tracker/src/store.rs index 6e8c5e1..b8ef97b 100644 --- a/tracker/src/store.rs +++ b/tracker/src/store.rs @@ -90,11 +90,7 @@ impl Store { /// database transaction. Re-inserting the same `(tx_hash, source_name)` /// pair is silently ignored (`ON CONFLICT DO NOTHING`). Returns the /// number of rows actually inserted. - pub async fn apply_block( - &self, - cursor: ChainPoint, - rows: Vec, - ) -> Result { + pub async fn apply_block(&self, cursor: ChainPoint, rows: Vec) -> Result { let mut tx = self.pool.begin().await?; let mut inserted = 0usize; diff --git a/tracker/tests/discovery.rs b/tracker/tests/discovery.rs index 4a73db9..c8cdb22 100644 --- a/tracker/tests/discovery.rs +++ b/tracker/tests/discovery.rs @@ -424,7 +424,8 @@ async fn fetch_catalog_errors_on_empty_catalog() { .expect_err("should fail on empty catalog"); assert!( - err.to_string().contains("OCI registry returned no protocols"), + err.to_string() + .contains("OCI registry returned no protocols"), "error message should mention empty catalog, got: {err}" ); } @@ -472,10 +473,7 @@ async fn fetch_catalog_errors_on_missing_tii_layer() { .and(path("/v2/txpipe/orcfax-burn/manifests/1.0.0")) .respond_with( ResponseTemplate::new(200) - .insert_header( - "Content-Type", - "application/vnd.oci.image.manifest.v1+json", - ) + .insert_header("Content-Type", "application/vnd.oci.image.manifest.v1+json") .insert_header("Docker-Content-Digest", bad_manifest_digest.as_str()) .set_body_bytes(bad_manifest), ) @@ -504,9 +502,7 @@ async fn fetch_catalog_errors_on_missing_tii_layer() { let layer_bytes = b"fake tx3 blob".to_vec(); let layer_digest = sha256_hex(&layer_bytes); Mock::given(method("GET")) - .and(path(format!( - "/v2/txpipe/orcfax-burn/blobs/{layer_digest}" - ))) + .and(path(format!("/v2/txpipe/orcfax-burn/blobs/{layer_digest}"))) .respond_with( ResponseTemplate::new(200) .insert_header("Docker-Content-Digest", layer_digest.as_str()) @@ -597,7 +593,9 @@ async fn fetch_catalog_ignores_logo_png_layer() { let config_bytes = dummy_config_bytes(); let config_digest = sha256_hex(&config_bytes); Mock::given(method("GET")) - .and(path(format!("/v2/txpipe/orcfax-burn/blobs/{config_digest}"))) + .and(path(format!( + "/v2/txpipe/orcfax-burn/blobs/{config_digest}" + ))) .respond_with( ResponseTemplate::new(200) .insert_header("Docker-Content-Digest", config_digest.as_str()) diff --git a/tracker/tests/gating_real_txs.rs b/tracker/tests/gating_real_txs.rs index 23d8b86..7b3cd91 100644 --- a/tracker/tests/gating_real_txs.rs +++ b/tracker/tests/gating_real_txs.rs @@ -39,8 +39,8 @@ fn source(name: &str) -> DiscoveredSource { /// → `total == 1`, `gates() == false`. #[test] fn dex_swap_iusd_does_not_gate_indigo() { - let active = specialize_all(&[source("indigo")]) - .expect("specialize_all on indigo/mainnet must succeed"); + let active = + specialize_all(&[source("indigo")]).expect("specialize_all on indigo/mainnet must succeed"); assert_eq!(active.len(), 1, "indigo/mainnet must survive the filter"); let anchors = &active[0].anchors; @@ -72,8 +72,8 @@ fn dex_swap_iusd_does_not_gate_indigo() { /// the indigo/mainnet anchor set: `gates() == true` and `gating >= 1`. #[test] fn indigo_create_staking_gates_indigo() { - let active = specialize_all(&[source("indigo")]) - .expect("specialize_all on indigo/mainnet must succeed"); + let active = + specialize_all(&[source("indigo")]).expect("specialize_all on indigo/mainnet must succeed"); assert_eq!(active.len(), 1, "indigo/mainnet must survive the filter"); let anchors = &active[0].anchors; diff --git a/tracker/tests/migration_repo_columns.rs b/tracker/tests/migration_repo_columns.rs index d1aedaa..c6d5b03 100644 --- a/tracker/tests/migration_repo_columns.rs +++ b/tracker/tests/migration_repo_columns.rs @@ -66,5 +66,8 @@ async fn idx_matches_repo_exists(pool: PgPool) { .await .expect("pg_indexes query failed"); - assert!(row.is_some(), "index idx_matches_repo not found in pg_indexes"); + assert!( + row.is_some(), + "index idx_matches_repo not found in pg_indexes" + ); } diff --git a/tracker/tests/source_anchors.rs b/tracker/tests/source_anchors.rs index 1d3bfcd..9a4bbac 100644 --- a/tracker/tests/source_anchors.rs +++ b/tracker/tests/source_anchors.rs @@ -3,10 +3,8 @@ use tx3_registry_tracker::specialization::{specialize_all, SpecializedTii}; use tx3_sdk::tii::spec::TiiFile; fn anchored_source() -> DiscoveredSource { - let tii: TiiFile = serde_json::from_str(include_str!( - "fixtures/orcfax_burn_anchored.tii" - )) - .expect("failed to parse orcfax_burn_anchored.tii"); + let tii: TiiFile = serde_json::from_str(include_str!("fixtures/orcfax_burn_anchored.tii")) + .expect("failed to parse orcfax_burn_anchored.tii"); DiscoveredSource { source_name: "txpipe/orcfax-burn-anchored:1.0.0".to_string(), @@ -19,9 +17,8 @@ fn anchored_source() -> DiscoveredSource { } fn anchorless_source() -> DiscoveredSource { - let tii: TiiFile = - serde_json::from_str(include_str!("fixtures/orcfax_burn_anchorless.tii")) - .expect("failed to parse orcfax_burn_anchorless.tii"); + let tii: TiiFile = serde_json::from_str(include_str!("fixtures/orcfax_burn_anchorless.tii")) + .expect("failed to parse orcfax_burn_anchorless.tii"); DiscoveredSource { source_name: "txpipe/orcfax-burn:1.0.0".to_string(), @@ -88,8 +85,5 @@ fn anchored_source_alone_is_retained() { specialize_all(&sources).expect("specialize_all on anchored source must succeed"); assert_eq!(active.len(), 1, "anchored source must survive the filter"); - assert!( - !active[0].anchors.is_empty(), - "anchors must be non-empty" - ); + assert!(!active[0].anchors.is_empty(), "anchors must be non-empty"); } diff --git a/tracker/tests/store_idempotency.rs b/tracker/tests/store_idempotency.rs index f608809..1dd43b9 100644 --- a/tracker/tests/store_idempotency.rs +++ b/tracker/tests/store_idempotency.rs @@ -43,7 +43,10 @@ async fn reinserting_same_match_is_noop(pool: PgPool) { .expect("second apply_block failed"); assert_eq!(n1, 1, "first apply_block should insert exactly one row"); - assert_eq!(n2, 0, "second apply_block must be a no-op: UNIQUE(tx_hash, source_name) + ON CONFLICT DO NOTHING"); + assert_eq!( + n2, 0, + "second apply_block must be a no-op: UNIQUE(tx_hash, source_name) + ON CONFLICT DO NOTHING" + ); } #[sqlx::test(migrations = "./migrations")] @@ -67,5 +70,8 @@ async fn apply_block_persists_score_and_rank(pool: PgPool) { let db_rank: i32 = result.get(1); assert_eq!(db_score, row.score as i32, "score must be persisted"); - assert_eq!(db_rank, row.match_rank as i32, "match_rank must be persisted"); + assert_eq!( + db_rank, row.match_rank as i32, + "match_rank must be persisted" + ); } diff --git a/tracker/tests/store_repo_columns.rs b/tracker/tests/store_repo_columns.rs index 00ce299..5a9f034 100644 --- a/tracker/tests/store_repo_columns.rs +++ b/tracker/tests/store_repo_columns.rs @@ -114,7 +114,10 @@ async fn apply_block_handles_multiple_versions_distinctly(pool: PgPool) { .expect("count query failed"); let count: i64 = count_row.get(0); - assert_eq!(count, 2, "expected two distinct rows for different versions"); + assert_eq!( + count, 2, + "expected two distinct rows for different versions" + ); let version_rows = sqlx::query( "SELECT repo_version FROM matches WHERE repo_scope = 'txpipe' AND repo_name = 'orcfax-burn' ORDER BY repo_version", diff --git a/zot/seed/Cargo.lock b/zot/seed/Cargo.lock new file mode 100644 index 0000000..377b0f6 --- /dev/null +++ b/zot/seed/Cargo.lock @@ -0,0 +1,1788 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[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 = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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 = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "getset" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-auth" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" +dependencies = [ + "memchr", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oci-client" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474675fdc023fbcc9dcf4782e938a3a1ae5fd469c728d8db40599bd25c77e1ba" +dependencies = [ + "bytes", + "chrono", + "futures-util", + "http", + "http-auth", + "jwt", + "lazy_static", + "oci-spec", + "olpc-cjson", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "oci-spec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da406e58efe2eb5986a6139626d611ce426e5324a824133d76367c765cf0b882" +dependencies = [ + "derive_builder", + "getset", + "regex", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 2.0.19", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696183c9b5fe81a7715d074fd632e8bd46f4ccc0231a3ed7fc580a80de5f7083" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[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.119", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[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.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[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.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[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 = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zot_seed" +version = "0.1.0" +dependencies = [ + "chrono", + "oci-client", + "serde", + "serde_json", + "tokio", +] diff --git a/zot/seed/Cargo.toml b/zot/seed/Cargo.toml index b9cec4f..d03011f 100644 --- a/zot/seed/Cargo.toml +++ b/zot/seed/Cargo.toml @@ -2,6 +2,7 @@ name = "zot_seed" version = "0.1.0" edition = "2021" +publish = false [dependencies] oci-client = "0.14.0" diff --git a/zot/seed/main.rs b/zot/seed/main.rs index 851784d..9437c8e 100644 --- a/zot/seed/main.rs +++ b/zot/seed/main.rs @@ -1,11 +1,16 @@ -use std::fs; -use oci_client::{client::{Config, ImageLayer}, manifest, secrets::RegistryAuth, Client, Reference}; +use oci_client::{ + client::{Config, ImageLayer}, + manifest, + secrets::RegistryAuth, + Client, Reference, +}; use serde::{Deserialize, Serialize}; use serde_json::Number; +use std::fs; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct DataJson { - pub protocols: Vec + pub protocols: Vec, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -41,26 +46,24 @@ fn get_client() -> Client { const MARKDOWN_MEDIA_TYPE: &str = "text/markdown"; const PROTOCOL_MEDIA_TYPE: &str = "application/tx3"; -async fn push(protocol: &ProtocolJson, protocol_file: String) -> Result<(), Box> { - +async fn push( + protocol: &ProtocolJson, + protocol_file: String, +) -> Result<(), Box> { let mut layers = vec![]; - layers.push( - ImageLayer::new( - protocol_file.as_bytes().to_vec(), - PROTOCOL_MEDIA_TYPE.to_string(), - None - ) - ); + layers.push(ImageLayer::new( + protocol_file.as_bytes().to_vec(), + PROTOCOL_MEDIA_TYPE.to_string(), + None, + )); if protocol.readme.is_some() { - layers.push( - ImageLayer::new( - protocol.readme.clone().unwrap().as_bytes().to_vec(), - MARKDOWN_MEDIA_TYPE.to_string(), - None - ) - ); + layers.push(ImageLayer::new( + protocol.readme.clone().unwrap().as_bytes().to_vec(), + MARKDOWN_MEDIA_TYPE.to_string(), + None, + )); } let config = Config { @@ -82,25 +85,48 @@ async fn push(protocol: &ProtocolJson, protocol_file: String) -> Result<(), Box< Some(std::collections::BTreeMap::from([ ( "org.opencontainers.image.created".to_string(), - chrono::DateTime::from_timestamp(protocol.published_date.as_i64().unwrap_or_default(), 0) + chrono::DateTime::from_timestamp( + protocol.published_date.as_i64().unwrap_or_default(), + 0, + ) .unwrap() - .to_rfc3339() + .to_rfc3339(), + ), + ( + "org.opencontainers.image.vendor".to_string(), + protocol.scope.clone(), + ), + ( + "org.opencontainers.image.title".to_string(), + protocol.name.clone(), ), - ("org.opencontainers.image.vendor".to_string(), protocol.scope.clone()), - ("org.opencontainers.image.title".to_string(), protocol.name.clone()), - ("org.opencontainers.image.version".to_string(), "1.0.0".to_string()), - ("org.opencontainers.image.source".to_string(), protocol.repository_url.clone().unwrap_or_default()), - ("org.opencontainers.image.description".to_string(), protocol.description.clone().unwrap_or_default()), - ])) + ( + "org.opencontainers.image.version".to_string(), + "1.0.0".to_string(), + ), + ( + "org.opencontainers.image.source".to_string(), + protocol.repository_url.clone().unwrap_or_default(), + ), + ( + "org.opencontainers.image.description".to_string(), + protocol.description.clone().unwrap_or_default(), + ), + ])), ); - let reference = Reference::try_from(format!("localhost:3000/{}/{}:1.0.0", protocol.scope, protocol.name))?; + let reference = Reference::try_from(format!( + "localhost:3000/{}/{}:1.0.0", + protocol.scope, protocol.name + ))?; let client = get_client(); let auth = RegistryAuth::Anonymous; - let digest = client.push(&reference, &layers, config, &auth, Some(image_manifest)).await?; + let digest = client + .push(&reference, &layers, config, &auth, Some(image_manifest)) + .await?; println!("Config URL: {}", digest.config_url); println!("Manifest URL: {}", digest.manifest_url); @@ -109,14 +135,19 @@ async fn push(protocol: &ProtocolJson, protocol_file: String) -> Result<(), Box< } async fn pull(repo: &str, version: &str) -> Result<(), Box> { - let reference = Reference::try_from(format!("localhost:3000/{}:{}", repo, version))?; let client = get_client(); let auth = RegistryAuth::Anonymous; - let content = client.pull(&reference, &auth, vec![MARKDOWN_MEDIA_TYPE, PROTOCOL_MEDIA_TYPE]).await?; + let content = client + .pull( + &reference, + &auth, + vec![MARKDOWN_MEDIA_TYPE, PROTOCOL_MEDIA_TYPE], + ) + .await?; println!("Config Metadata: {:?}", content.config.data); @@ -134,11 +165,12 @@ async fn main() -> Result<(), Box> { let data: DataJson = serde_json::from_str(&json).expect("Unable to parse"); for protocol in data.protocols.iter() { - let protocol_file = fs::read_to_string(format!("../../data/{}", protocol.protocol_path)).expect("Unable to read file"); + let protocol_file = fs::read_to_string(format!("../../data/{}", protocol.protocol_path)) + .expect("Unable to read file"); push(protocol, protocol_file).await?; } // pull("txpipe/asteria", "1.0.0").await?; Ok(()) -} \ No newline at end of file +}