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
use super::{ImageArray, ImageBytesMetadata, COLOUR_AXIS, IMAGE_BYTES_TYPE};
use crate::api::{ImageArrayRank, ImageElementType, TransmissionElementType};
use crate::server::Response;
use crate::ASCOMResult;
use bytemuck::{bytes_of, Zeroable};
use serde::{Serialize, Serializer};
use std::mem::size_of;

pub(crate) struct ImageBytesResponse(pub(crate) ImageArray);

impl Response for ASCOMResult<ImageBytesResponse> {
    fn into_axum(
        self,
        transaction: crate::server::ResponseTransaction,
    ) -> axum::response::Response {
        use axum::response::IntoResponse;

        let mut metadata = ImageBytesMetadata {
            metadata_version: 1,
            data_start: i32::try_from(size_of::<ImageBytesMetadata>())
                .expect("internal error: metadata size is too large"),
            client_transaction_id: transaction.client_transaction_id,
            server_transaction_id: Some(transaction.server_transaction_id),
            ..Zeroable::zeroed()
        };
        let bytes = match &self {
            Ok(ImageBytesResponse(img_array)) => {
                metadata.image_element_type = ImageElementType::I32.into();
                metadata.transmission_element_type = img_array.transmission_element_type.into();
                let dims = {
                    let (dim0, dim1, dim2) = img_array.dim();
                    [dim0, dim1, dim2]
                        .map(|dim| i32::try_from(dim).expect("dimension is too large"))
                };
                metadata.dimension_1 = dims[0];
                metadata.dimension_2 = dims[1];
                metadata.rank = match dims[2] {
                    1_i32 => ImageArrayRank::Rank2,
                    n => {
                        metadata.dimension_3 = n;
                        ImageArrayRank::Rank3
                    }
                }
                .into();
                let mut bytes = Vec::with_capacity(
                    size_of::<ImageBytesMetadata>()
                        + img_array.len()
                            * match img_array.transmission_element_type {
                                TransmissionElementType::I32 => size_of::<i32>(),
                                TransmissionElementType::U8 => size_of::<u8>(),
                                TransmissionElementType::I16 => size_of::<i16>(),
                                TransmissionElementType::U16 => size_of::<u16>(),
                            },
                );
                bytes.extend_from_slice(bytes_of(&metadata));
                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
                match img_array.transmission_element_type {
                    TransmissionElementType::I32 => {
                        bytes.extend(img_array.iter().flat_map(|&i| i.to_le_bytes()));
                    }
                    TransmissionElementType::U8 => {
                        bytes.extend(img_array.iter().map(|&i| i as u8));
                    }
                    TransmissionElementType::I16 => {
                        bytes.extend(img_array.iter().flat_map(|&i| (i as i16).to_le_bytes()));
                    }
                    TransmissionElementType::U16 => {
                        bytes.extend(img_array.iter().flat_map(|&i| (i as u16).to_le_bytes()));
                    }
                }
                bytes
            }
            Err(err) => {
                metadata.error_number = err.code.raw().into();
                let mut bytes =
                    Vec::with_capacity(size_of::<ImageBytesMetadata>() + err.message.len());
                bytes.extend_from_slice(bytes_of(&metadata));
                bytes.extend_from_slice(err.message.as_bytes());
                bytes
            }
        };
        (
            [(axum::http::header::CONTENT_TYPE, IMAGE_BYTES_TYPE)],
            bytes,
        )
            .into_response()
    }
}

impl Serialize for ImageArray {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        #[derive(Serialize)]
        #[serde(rename_all = "PascalCase")]
        struct JsonImageArray<'img> {
            #[serde(rename = "Type")]
            type_: ImageElementType,
            rank: ImageArrayRank,
            value: Value<'img>,
        }

        #[derive(Serialize)]
        #[serde(untagged)]
        enum Value<'img> {
            Rank2(#[serde(with = "serde_ndim")] ndarray::ArrayView2<'img, i32>),
            Rank3(#[serde(with = "serde_ndim")] ndarray::ArrayView3<'img, i32>),
        }

        let view = self.data.view();

        JsonImageArray {
            type_: ImageElementType::I32,
            rank: self.rank(),
            value: match self.rank() {
                ImageArrayRank::Rank2 => Value::Rank2(view.remove_axis(COLOUR_AXIS)),
                ImageArrayRank::Rank3 => Value::Rank3(view),
            },
        }
        .serialize(serializer)
    }
}

impl ImageArray {
    pub(crate) fn is_accepted(headers: &axum::headers::HeaderMap) -> bool {
        use mediatype::{MediaType, MediaTypeList};

        const MEDIA_TYPE: MediaType<'static> = MediaType::new(
            mediatype::names::APPLICATION,
            mediatype::Name::new_unchecked("imagebytes"),
        );

        headers
            .get_all(axum::http::header::ACCEPT)
            .iter()
            .filter_map(|value| value.to_str().ok())
            .flat_map(MediaTypeList::new)
            .filter_map(std::result::Result::ok)
            .any(|media_type| media_type.essence() == MEDIA_TYPE)
    }
}