1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use http::HeaderMap;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

use crate::custom_serde::{deserialize_headers, serialize_headers};

/// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas
/// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct S3ObjectLambdaEvent<P = Value>
where
    P: DeserializeOwned,
    P: Serialize,
{
    pub x_amz_request_id: String,
    pub get_object_context: Option<GetObjectContext>,
    pub head_object_context: Option<HeadObjectContext>,
    pub list_objects_context: Option<ListObjectsContext>,
    pub list_objects_v2_context: Option<ListObjectsV2Context>,
    #[serde(default, bound = "")]
    pub configuration: Configuration<P>,
    pub user_request: UserRequest,
    pub user_identity: UserIdentity,
    pub protocol_version: String,
}

/// `GetObjectContext` contains the input and output details
/// for connections to Amazon S3 and S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GetObjectContext {
    pub input_s3_url: String,
    pub output_route: String,
    pub output_token: String,
}

/// `HeadObjectContext`
/// for connections to Amazon S3 and S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadObjectContext {
    pub input_s3_url: String,
}

/// `ListObjectsContext`
/// for connections to Amazon S3 and S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListObjectsContext {
    pub input_s3_url: String,
}

/// `ListObjectsV2Context`
/// for connections to Amazon S3 and S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListObjectsV2Context {
    pub input_s3_url: String,
}

/// `Configuration` contains information about the Object Lambda access point
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Configuration<P = Value>
where
    P: DeserializeOwned,
    P: Serialize,
{
    pub access_point_arn: String,
    pub supporting_access_point_arn: String,
    #[serde(default, bound = "")]
    pub payload: P,
}

/// `UserRequest` contains information about the original call to S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UserRequest {
    pub url: String,
    #[serde(deserialize_with = "deserialize_headers", default)]
    #[serde(serialize_with = "serialize_headers")]
    pub headers: HeaderMap,
}

/// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]

pub struct UserIdentity {
    pub r#type: String,
    pub principal_id: String,
    pub arn: String,
    pub account_id: String,
    pub access_key_id: String,
    pub session_context: Option<SessionContext>,
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionContext {
    pub attributes: HashMap<String, String>,
    #[serde(default)]
    pub session_issuer: Option<SessionIssuer>,
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionIssuer {
    pub r#type: String,
    pub principal_id: String,
    pub arn: String,
    pub account_id: String,
    pub user_name: String,
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    #[cfg(feature = "s3")]
    fn example_object_lambda_event_get_object_assumed_role() {
        let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-assumed-role.json");
        let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap();
        let output: String = serde_json::to_string(&parsed).unwrap();
        let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
        assert_eq!(parsed, reparsed);
    }

    #[test]
    #[cfg(feature = "s3")]
    fn example_object_lambda_event_get_object_iam() {
        let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-iam.json");
        let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap();
        let output: String = serde_json::to_string(&parsed).unwrap();
        let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
        assert_eq!(parsed, reparsed);
    }

    #[test]
    #[cfg(feature = "s3")]
    fn example_object_lambda_event_head_object_iam() {
        let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-head-object-iam.json");
        let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap();
        let output: String = serde_json::to_string(&parsed).unwrap();
        let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
        assert_eq!(parsed, reparsed);
    }

    #[test]
    #[cfg(feature = "s3")]
    fn example_object_lambda_event_list_objects_iam() {
        let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-iam.json");
        let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap();
        let output: String = serde_json::to_string(&parsed).unwrap();
        let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
        assert_eq!(parsed, reparsed);
    }

    #[test]
    #[cfg(feature = "s3")]
    fn example_object_lambda_event_list_objects_v2_iam() {
        let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json");
        let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap();
        let output: String = serde_json::to_string(&parsed).unwrap();
        let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap();
        assert_eq!(parsed, reparsed);
    }
}