shelly-shell 0.5.4

A Rust based Unix style shell with a typed and structured language syntax.

use crate::{
    language::{
        data::{
            io::{Bytes, adapters::ByteSource, handles::Reader},
            native_value::{NativeData, NativeValue},
            net::server::{Incoming, Outgoing, Reply, Server},
            value::Value,
        },
        interpreter::{ErrorWhat, Interpreter, InterpreterError, InterpreterResult},
        native::NativeOutcome,
        text::location::Location,
    },
    runtime::tasks::NativeFault,
};

use super::http::{body_content_type, field, header_values, headers, string};

fn native<T: NativeData>(value: &Value) -> Option<&T>
{
    if let Value::Native(value) = value.underlying()
    {
        value.downcast()
    }
    else
    {
        None
    }
}
fn data(value: impl NativeData) -> Value
{
    Value::Native(NativeValue::new(value))
}

impl Interpreter
{
    pub(in crate::language::interpreter) fn handle_server(
        &mut self,
        _: &Location,
        args: &[Value],
    ) -> NativeOutcome
    {
        let result = self.server_command(args);
        result.unwrap_or_else(|error| {
            NativeOutcome::Ready(self.io_fault(&error.kind, "http.server", error.message))
        })
    }
    fn server_command(&mut self, args: &[Value]) -> Result<NativeOutcome, NativeFault>
    {
        let [command, input, rest @ ..] = args
        else
        {
            return Err(NativeFault::new("OpenError", "Missing server operation"));
        };
        let command = string(command)?;
        match command
        {
            "listen" =>
            {
                let host = string(input)?.to_string();
                let Some(Value::Integer(port)) = rest.first()
                else
                {
                    return Err(NativeFault::new("OpenError", "Missing server port"));
                };
                let port = u16::try_from(*port)
                    .map_err(|_| NativeFault::new("OpenError", "Invalid port"))?;
                Ok(self.native_wait(command, async move { Server::bind(host, port).await }, data))
            }
            "address" =>
            {
                let server = native::<Server>(input)
                    .ok_or_else(|| NativeFault::new("OpenError", "Expected Server"))?;
                let address = server.address();
                Ok(NativeOutcome::Ready(Value::from_array(vec![
                    Value::from_string(address.ip().to_string()),
                    Value::Integer(address.port() as i64),
                ])))
            }
            "accept" =>
            {
                let server = native::<Server>(input)
                    .cloned()
                    .ok_or_else(|| NativeFault::new("OpenError", "Expected Server"))?;
                Ok(self.native_wait(command, async move { server.accept().await }, data))
            }
            "method" | "path" | "headers" | "take_body" =>
            {
                let request = native::<Incoming>(input)
                    .ok_or_else(|| NativeFault::new("OpenError", "Expected Incoming"))?;
                Ok(NativeOutcome::Ready(match command
                {
                    "method" => Value::from_string(request.method().into()),
                    "path" => Value::from_string(request.path().into()),
                    "headers" => header_values(request.headers()),
                    _ => data(request.take_body()?),
                }))
            }
            "respond" =>
            {
                let request = native::<Incoming>(input)
                    .cloned()
                    .ok_or_else(|| NativeFault::new("OpenError", "Expected Incoming"))?;
                let response = rest
                    .first()
                    .ok_or_else(|| NativeFault::new("OpenError", "Missing Response"))?;
                let status = field(response, "status")
                    .and_then(|value| {
                        if let Value::Integer(status) = value
                        {
                            Some(*status)
                        }
                        else
                        {
                            None
                        }
                    })
                    .unwrap_or(200);
                if !(200..=599).contains(&status)
                {
                    return Err(NativeFault::new(
                        "OpenError",
                        "Server status must be 200..599",
                    ));
                }
                let mut response_headers = headers(field(response, "headers"))?;
                let body = match field(response, "body")
                {
                    None => Outgoing::Empty,
                    Some(body) =>
                    {
                        if let Some(content_type) = field(body, "content_type")
                        {
                            body_content_type(&mut response_headers, string(content_type)?)?;
                        }
                        let value = field(body, "value").unwrap_or(body);
                        if let Some(bytes) = native::<Bytes>(value)
                        {
                            Outgoing::Bytes(bytes.clone())
                        }
                        else if let Some(reader) = native::<Reader>(value)
                        {
                            Outgoing::Reader(reader.clone())
                        }
                        else if let Some(source) = native::<ByteSource>(value)
                        {
                            Outgoing::Source(source.clone())
                        }
                        else
                        {
                            return Err(NativeFault::new("OpenError", "Unsupported server body"));
                        }
                    }
                };
                let response = Reply {
                    status: status as u16,
                    headers: response_headers,
                    body,
                };
                Ok(self.native_wait(
                    command,
                    async move { request.respond(response).await },
                    |_| Value::None,
                ))
            }
            _ => Err(NativeFault::new("OpenError", "Unknown server operation")),
        }
    }
    pub(in crate::language::interpreter) fn handle_server_close(
        &mut self,
        _: &Location,
        args: &[Value],
    ) -> NativeOutcome
    {
        if let Some(value) = args.first()
        {
            if let Some(server) = native::<Server>(value)
            {
                server.close();
            }
            if let Some(request) = native::<Incoming>(value).cloned()
            {
                return self.native_wait(
                    "http.request.close",
                    async move { request.finish_close().await },
                    |_| Value::None,
                );
            }
        }
        NativeOutcome::Ready(Value::None)
    }
    pub(in crate::language::interpreter) fn handle_server_invoke(
        &mut self,
        location: &Location,
        args: &[Value],
    ) -> InterpreterResult<()>
    {
        let [handler, request] = args
        else
        {
            return Err(InterpreterError::new(
                location.clone(),
                ErrorWhat::ArgumentMismatch("Handler requires request".into()),
            ));
        };
        let incoming = native::<Incoming>(request).ok_or_else(|| {
            InterpreterError::new(
                location.clone(),
                ErrorWhat::ArgumentMismatch("Expected Incoming".into()),
            )
        })?;
        let previous = self
            .exceptions
            .operation
            .replace((vec![incoming.cancelled()], incoming.deadline()));
        let result = self.execute_value(location, handler.clone(), vec![request.clone()]);
        self.exceptions.operation = previous;
        result
    }
}