Skip to main content

ossify/ops/object/base/
seal_append_object.rs

1//! SealAppendObject operation.
2//!
3//! Stops appending content to an Appendable Object, making it non-appendable.
4//! After sealing, the storage class can later be transitioned to Cold Archive
5//! or Deep Cold Archive via lifecycle rules.
6//!
7//! Official document: <https://www.alibabacloud.com/help/en/oss/developer-reference/sealappendobject>
8
9use std::future::Future;
10
11use http::Method;
12use serde::{Deserialize, Serialize};
13
14use crate::body::NoneBody;
15use crate::error::Result;
16use crate::response::HeaderResponseProcessor;
17use crate::ser::OnlyKeyField;
18use crate::{Client, Ops, Prepared, Request};
19
20/// SealAppendObject query parameters: `?seal&position=<n>`.
21#[derive(Debug, Clone, Serialize)]
22pub struct SealAppendObjectParams {
23    pub(crate) seal: OnlyKeyField,
24    pub position: u64,
25}
26
27impl SealAppendObjectParams {
28    /// Create new parameters with the expected object length.
29    pub fn new(position: u64) -> Self {
30        Self {
31            seal: OnlyKeyField,
32            position,
33        }
34    }
35}
36
37/// SealAppendObject response headers.
38#[derive(Debug, Clone, Deserialize)]
39pub struct SealAppendObjectResponse {
40    /// The time when the object was first sealed (RFC 2822 GMT format).
41    #[serde(rename = "x-oss-sealed-time")]
42    pub sealed_time: Option<String>,
43    /// Object ETag.
44    #[serde(rename = "etag")]
45    pub etag: Option<String>,
46}
47
48/// SealAppendObject operation.
49pub struct SealAppendObject {
50    pub object_key: String,
51    pub params: SealAppendObjectParams,
52}
53
54impl Ops for SealAppendObject {
55    type Response = HeaderResponseProcessor<SealAppendObjectResponse>;
56    type Body = NoneBody;
57    type Query = SealAppendObjectParams;
58
59    fn prepare(self) -> Result<Prepared<SealAppendObjectParams>> {
60        Ok(Prepared {
61            method: Method::POST,
62            key: Some(self.object_key),
63            query: Some(self.params),
64            ..Default::default()
65        })
66    }
67}
68
69/// Trait for SealAppendObject operations.
70pub trait SealAppendObjectOperations {
71    /// Seal an Appendable Object, preventing further appends.
72    fn seal_append_object(
73        &self,
74        object_key: impl Into<String>,
75        position: u64,
76    ) -> impl Future<Output = Result<SealAppendObjectResponse>>;
77}
78
79impl SealAppendObjectOperations for Client {
80    async fn seal_append_object(
81        &self,
82        object_key: impl Into<String>,
83        position: u64,
84    ) -> Result<SealAppendObjectResponse> {
85        let ops = SealAppendObject {
86            object_key: object_key.into(),
87            params: SealAppendObjectParams::new(position),
88        };
89        self.request(ops).await
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_serialize_params() {
99        let params = SealAppendObjectParams::new(344606);
100        let query = crate::ser::to_string(&params).unwrap();
101        assert_eq!(query, "position=344606&seal");
102    }
103
104    #[test]
105    fn test_deserialize_response() {
106        // HeaderResponseProcessor converts headers into a map and then
107        // deserializes from JSON, so we simulate the same path here.
108        let json = serde_json::json!({
109            "x-oss-sealed-time": "Wed, 07 May 2025 23:00:00 GMT",
110            "etag": "\"abc\"",
111        });
112        let resp: SealAppendObjectResponse = serde_json::from_value(json).unwrap();
113        assert_eq!(resp.sealed_time.as_deref(), Some("Wed, 07 May 2025 23:00:00 GMT"));
114        assert_eq!(resp.etag.as_deref(), Some("\"abc\""));
115    }
116}