오류가 있는 Cloud Storage 객체 쓰기 취소

Rust용 Cloud 클라이언트 라이브러리의 Cloud Storage 클라이언트는 StreamingSource 특성을 구현하는 유형에서 페이로드 청크를 가져와 객체를 작성합니다. 데이터를 생성하거나 스트리밍하는 동안 애플리케이션에서 오류 조건을 감지하면 StreamingSource 구현에서 오류를 반환하여 클라이언트 라이브러리가 객체 생성을 완료하지 못하도록 합니다.

이 문서에서는 커스텀 오류 유형을 정의하고 StreamingSource를 구현하여 스트림 중간에 해당 오류를 반환하는 방법을 보여줍니다. 또한 실패한 업로드가 객체를 만들지 않고 오류를 반환하는지 확인합니다. 클라이언트 라이브러리를 처음 사용하는 경우 먼저 Rust에서 Cloud Storage 사용을 참조하세요.

시작하기 전에

이 문서의 코드를 실행하기 전에 다음 기본 요건을 완료하세요.

종속 항목

Cloud Storage 클라이언트 라이브러리 및 지원 크레이트를 Cargo.toml 파일에 추가합니다.

cargo add google-cloud-storage anyhow bytes

커스텀 오류 유형 만들기

완료 전에 객체 쓰기를 취소하려면 StreamingSource 구현에서 std::error::Error + Send + Sync + 'static를 구현하는 오류를 반환해야 합니다. 이러한 경계를 충족하는 기존 오류 유형을 사용하거나 애플리케이션의 커스텀 enum을 정의할 수 있습니다. 커스텀 오류 enum을 정의하려면 다음 단계를 완료하세요.

  1. 커스텀 오류 enum(MyError)을 정의합니다.

    #[derive(Debug)]
    pub enum MyError {
        ExpectedProblem,
        OhNoes,
    }

  2. 클라이언트 라이브러리가 오류를 근본 원인으로 보존할 수 있도록 표준 Error 특성을 구현합니다.

    impl std::error::Error for MyError {}

  3. std::error::Error에 필요한 Display 트레잇을 구현합니다.

    impl std::fmt::Display for MyError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                Self::ExpectedProblem => write!(f, "this kind of thing happens"),
                Self::OhNoes => write!(f, "oh noes! something terrible happened"),
            }
        }
    }

커스텀 스트리밍 소스 구현

다음으로 합성 청크를 생성하고 41개의 청크 후에 오류를 반환하는 유형을 만들어 완료 전에 업로드가 중지되도록 합니다. 다음 단계를 완료합니다.

  1. 청크 카운터를 추적하는 구조체를 정의합니다.

    #[derive(Debug, Default)]
    struct MySource(u32);

  2. MySource에 대해 StreamingSource를 구현하고 type Error = MyError를 연결하고 반복 42에서 Some(Err(MyError::ExpectedProblem))를 반환합니다.

    impl google_cloud_storage::streaming_source::StreamingSource for MySource {
        type Error = MyError;
        async fn next(&mut self) -> Option<Result<bytes::Bytes, Self::Error>> {
            self.0 += 1;
            match self.0 {
                42 => Some(Err(MyError::ExpectedProblem)),
                n if n > 42 => None,
                n => Some(Ok(bytes::Bytes::from_owner(format!(
                    "test data for the example {n}\n"
                )))),
            }
        }
    }

next가 Some(Err(...))를 반환하면 클라이언트 라이브러리는 소스에서 읽기를 중지하고 객체를 완료하지 않고 쓰기 요청을 취소합니다.

업로드 시도 및 오류 검사

StreamingSource를 구현한 후 버퍼링된 객체 쓰기를 시도하고 클라이언트가 MyError를 래핑하는 직렬화 오류를 반환하는지 확인합니다. MySource는 Seek를 구현하지 않는 단일 패스 소스이므로 send_unbuffered 대신 send_buffered로 쓰기를 시작합니다.

projects/_/buckets/BUCKET_ID 형식의 bucket_name를 허용하는 async 함수 내에서 다음 단계를 완료합니다.

  1. Storage 클라이언트를 초기화합니다.

    pub async fn attempt_upload(bucket_name: &str) -> anyhow::Result<()> {
        use google_cloud_storage::client::Storage;
        let client = Storage::builder().build().await?;

  2. MySource::default()를 페이로드 소스로 사용하여 버퍼링된 객체 쓰기를 시작합니다.

    let upload = client
        .write_object(bucket_name, "expect-error", MySource::default())
        .send_buffered()
        .await;

  3. 소스 오류가 MyError인 직렬화 오류로 인해 업로드가 실패하는지 확인합니다.

    println!("Upload result {upload:?}");
    let err = upload.expect_err("the source is supposed to terminate the upload");
    assert!(err.is_serialization(), "{err:?}");
    use std::error::Error as _;
    assert!(err.source().is_some_and(|e| e.is::<MyError>()), "{err:?}");

  4. Ok(())를 반환하여 함수를 완료합니다.

        Ok(())
    }

attempt_upload를 실행하면 MyError::ExpectedProblem가 포함된 Upload result Err(...) 메시지가 출력됩니다. 어설션은 클라이언트가 업로드를 중지하고 MyError를 래핑하는 직렬화 오류를 반환했는지 확인합니다. 완료 전에 쓰기가 취소되었으므로 Cloud Storage는 버킷에 expect-error 객체를 만들지 않습니다.

삭제

취소된 쓰기는 객체를 만들지 않지만 이 문서에 대해 만든 테스트 객체를 삭제합니다. 테스트를 완료한 후 테스트 리소스를 삭제하면 요금이 계속 청구되지 않습니다.

코드 완성

다음 예시에서는 이전의 모든 단계를 완전한 모듈로 결합합니다.

#[derive(Debug)]
pub enum MyError {
    ExpectedProblem,
    OhNoes,
}

impl std::error::Error for MyError {}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ExpectedProblem => write!(f, "this kind of thing happens"),
            Self::OhNoes => write!(f, "oh noes! something terrible happened"),
        }
    }
}

#[derive(Debug, Default)]
struct MySource(u32);

impl google_cloud_storage::streaming_source::StreamingSource for MySource {
    type Error = MyError;
    async fn next(&mut self) -> Option<Result<bytes::Bytes, Self::Error>> {
        self.0 += 1;
        match self.0 {
            42 => Some(Err(MyError::ExpectedProblem)),
            n if n > 42 => None,
            n => Some(Ok(bytes::Bytes::from_owner(format!(
                "test data for the example {n}\n"
            )))),
        }
    }
}

pub async fn attempt_upload(bucket_name: &str) -> anyhow::Result<()> {
    use google_cloud_storage::client::Storage;
    let client = Storage::builder().build().await?;
    let upload = client
        .write_object(bucket_name, "expect-error", MySource::default())
        .send_buffered()
        .await;
    println!("Upload result {upload:?}");
    let err = upload.expect_err("the source is supposed to terminate the upload");
    assert!(err.is_serialization(), "{err:?}");
    use std::error::Error as _;
    assert!(err.source().is_some_and(|e| e.is::<MyError>()), "{err:?}");
    Ok(())
}

다음 단계