ascom-alpaca 1.0.0-beta.10

Rust implementation of the ASCOM Alpaca API for astronomy devices
Documentation
use super::{
    ImageArray, ImageArrayRank, ImageBytesMetadata, ImageElementType, TransmissionElementType,
    COLOUR_AXIS, IMAGE_BYTES_TYPE,
};
use crate::server::ResponseWithTransaction;
use crate::ASCOMResult;
use axum::response::{IntoResponse, Response};
use bytemuck::{bytes_of, Zeroable};
use http::header::{HeaderMap, ACCEPT, CONTENT_TYPE};
use serde::{Serialize, Serializer};
use std::mem::size_of;

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

impl IntoResponse for ResponseWithTransaction<ASCOMResult<ImageBytesResponse>> {
    fn into_response(self) -> Response {
        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: self.transaction.client_transaction_id,
            server_transaction_id: Some(self.transaction.server_transaction_id),
            ..Zeroable::zeroed()
        };
        let bytes = match &self.response {
            Ok(ImageBytesResponse(img_array)) => {
                metadata.image_element_type = ImageElementType::I32.into();
                metadata.transmission_element_type = img_array.transmission_element_type.into();
                let dims = <[_; 3]>::from(img_array.dim())
                    .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));
                #[expect(
                    clippy::as_conversions,
                    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
            }
        };
        ([(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: &HeaderMap) -> bool {
        use mediatype::{names, MediaType, MediaTypeList, Name};

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

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