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
use std::path::PathBuf;

use hyper::{
    body::Bytes,
    header::{HeaderValue, CONTENT_TYPE},
    Response, StatusCode,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use spacegate_kernel::{extension::Reflect, BoxError, SgBody};

use crate::{Plugin, PluginError};

/// StaticResourceConfig
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct StaticResourceConfig {
    /// response status code
    pub code: u16,
    /// response content type
    pub content_type: String,
    /// response body
    pub body: BodyEnum,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", content = "value")]
pub enum BodyEnum {
    /// json value
    Json(Value),
    /// plain text
    Text(String),
    /// read a static file from file system
    File(PathBuf),
}

#[derive(Debug, Clone)]
pub struct StaticResource {
    pub code: StatusCode,
    pub content_type: HeaderValue,
    pub body: Bytes,
}

pub struct StaticResourcePlugin {
    pub code: StatusCode,
    pub content_type: HeaderValue,
    pub body: Bytes,
}

impl Plugin for StaticResourcePlugin {
    const CODE: &'static str = "static-resource";
    #[cfg(feature = "schema")]
    fn schema_opt() -> Option<schemars::schema::RootSchema> {
        Some(<Self as crate::PluginSchemaExt>::schema())
    }

    async fn call(&self, req: hyper::Request<SgBody>, _inner: spacegate_kernel::helper_layers::function::Inner) -> Result<Response<SgBody>, BoxError> {
        let mut resp = Response::builder()
            .header(CONTENT_TYPE, self.content_type.clone())
            .status(self.code)
            .body(SgBody::full(self.body.clone()))
            .map_err(PluginError::internal_error::<StaticResourcePlugin>)?;
        if let Some(reflect) = req.into_parts().0.extensions.remove::<Reflect>() {
            resp.extensions_mut().extend(reflect.into_inner());
        }
        Ok(resp)
    }

    fn create(config: crate::PluginConfig) -> Result<Self, spacegate_kernel::BoxError> {
        let plugin_config: StaticResourceConfig = serde_json::from_value(config.spec)?;
        let content_type = plugin_config.content_type.clone();
        let content_type = HeaderValue::from_maybe_shared(content_type)?;
        let body = match &plugin_config.body {
            BodyEnum::Json(value) => Bytes::copy_from_slice(value.to_string().as_bytes()),
            BodyEnum::Text(text) => Bytes::copy_from_slice(text.as_bytes()),
            BodyEnum::File(path) => Bytes::copy_from_slice(&std::fs::read(path)?),
        };
        let code = StatusCode::from_u16(plugin_config.code)?;
        Ok(Self { content_type, code, body })
    }
}

#[cfg(feature = "schema")]
crate::schema!(StaticResourcePlugin, StaticResourceConfig);