Skip to main content

interprex_test/
releases.rs

1use async_trait::async_trait;
2use bytes::Bytes;
3use futures_util::{TryStreamExt, stream};
4use interprex::{
5    AssetId, AssetStream, AssetStreamError, AssetUpload, NewRelease, ProviderError, Release,
6    ReleaseAsset, ReleaseId, ReleasesProvider, Repository, Result,
7};
8
9use crate::state::{FakeProvider, missing};
10
11#[async_trait]
12impl ReleasesProvider for FakeProvider {
13    async fn release_by_tag(&self, repository: &Repository, tag: &str) -> Result<Release> {
14        self.state
15            .read()
16            .await
17            .releases
18            .get(&(repository.clone(), tag.to_owned()))
19            .cloned()
20            .ok_or_else(|| missing(format!("release {tag} in {repository}")))
21    }
22
23    async fn create_release(
24        &self,
25        repository: &Repository,
26        release: &NewRelease,
27    ) -> Result<Release> {
28        let mut state = self.state.write().await;
29        state.next_release_id += 1;
30        let created = Release {
31            id: ReleaseId::new(state.next_release_id).expect("increment starts at one"),
32            tag: release.tag.clone(),
33            name: release.name.clone(),
34            body: release.body.clone(),
35            draft: release.draft,
36            prerelease: release.prerelease,
37            assets: Vec::new(),
38        };
39        state
40            .releases
41            .insert((repository.clone(), created.tag.clone()), created.clone());
42        Ok(created)
43    }
44
45    async fn upload_asset(
46        &self,
47        repository: &Repository,
48        release_id: ReleaseId,
49        name: &str,
50        label: Option<&str>,
51        upload: AssetUpload,
52    ) -> Result<ReleaseAsset> {
53        let (content_length, chunks) = upload.into_parts();
54        let chunks: Vec<Bytes> =
55            chunks
56                .try_collect()
57                .await
58                .map_err(|error| ProviderError::External {
59                    provider: "fake",
60                    operation: "read release asset upload",
61                    message: error.to_string(),
62                })?;
63        let actual_length = chunks.iter().try_fold(0_u64, |length, chunk| {
64            length.checked_add(chunk.len() as u64)
65        });
66        if actual_length != Some(content_length) {
67            return Err(ProviderError::InvalidInput {
68                provider: "fake",
69                fact: format!(
70                    "asset upload declared {content_length} bytes but yielded {}",
71                    actual_length.map_or_else(
72                        || "an overflowing length".to_owned(),
73                        |value| value.to_string()
74                    )
75                ),
76            });
77        }
78        let mut state = self.state.write().await;
79        state.next_asset_id += 1;
80        let asset = ReleaseAsset {
81            id: AssetId::new(state.next_asset_id).expect("increment starts at one"),
82            name: name.to_owned(),
83            label: label.map(str::to_owned),
84            size: content_length,
85            download_url: format!("memory://{}/{}", repository, state.next_asset_id),
86        };
87        let release = state
88            .releases
89            .values_mut()
90            .find(|release| release.id == release_id)
91            .ok_or_else(|| missing(format!("release {release_id:?}")))?;
92        release.assets.push(asset.clone());
93        state.assets.insert((repository.clone(), asset.id), chunks);
94        Ok(asset)
95    }
96
97    async fn download_asset(
98        &self,
99        repository: &Repository,
100        asset_id: AssetId,
101    ) -> Result<AssetStream> {
102        let chunks = self
103            .state
104            .read()
105            .await
106            .assets
107            .get(&(repository.clone(), asset_id))
108            .cloned()
109            .ok_or_else(|| missing(format!("asset {asset_id:?} in {repository}")))?;
110        Ok(Box::pin(stream::iter(
111            chunks.into_iter().map(Ok::<Bytes, AssetStreamError>),
112        )))
113    }
114}