mq-lsp 0.5.8

Language Server Protocol implementation for mq query language
Documentation
use std::borrow::Cow;
use tower_lsp_server::jsonrpc;
use tower_lsp_server::ls_types::ExecuteCommandParams;

pub fn response(params: ExecuteCommandParams) -> jsonrpc::Result<Option<serde_json::Value>> {
    if params.arguments.is_empty() {
        return Err(jsonrpc::Error {
            code: jsonrpc::ErrorCode::InvalidParams,
            message: Cow::Borrowed("No arguments provided"),
            data: None,
        });
    }

    match params.command.as_str() {
        "mq/run" => match params
            .arguments
            .iter()
            .map(|v| v.as_str())
            .collect::<Vec<_>>()
            .as_slice()
        {
            [Some(code), Some(input), Some(input_format)] => execute(code, input, Some(input_format)),
            [Some(code), Some(input)] => execute(code, input, None),
            _ => Err(jsonrpc::Error {
                code: jsonrpc::ErrorCode::InvalidParams,
                message: Cow::Borrowed("Invalid arguments"),
                data: None,
            }),
        },
        "mq/to_ast_json" => match params
            .arguments
            .iter()
            .map(|v| v.as_str())
            .collect::<Vec<_>>()
            .as_slice()
        {
            [Some(code)] => {
                let token_arena = mq_lang::Shared::new(mq_lang::SharedCell::new(mq_lang::Arena::new(1024)));
                let program = mq_lang::parse(code, token_arena).map_err(|e| jsonrpc::Error {
                    code: jsonrpc::ErrorCode::InvalidParams,
                    message: Cow::Owned(format!("Error: {}", e)),
                    data: None,
                })?;
                let ast_json = mq_lang::ast_to_json(&program).map_err(|e| jsonrpc::Error {
                    code: jsonrpc::ErrorCode::InvalidParams,
                    message: Cow::Owned(format!("Error: {}", e)),
                    data: None,
                })?;
                Ok(Some(serde_json::to_value(ast_json).unwrap()))
            }
            _ => Err(jsonrpc::Error {
                code: jsonrpc::ErrorCode::InvalidParams,
                message: Cow::Borrowed("Invalid arguments"),
                data: None,
            }),
        },
        _ => Err(jsonrpc::Error {
            code: jsonrpc::ErrorCode::InvalidParams,
            message: Cow::Borrowed("Invalid arguments"),
            data: None,
        }),
    }
}

fn execute(code: &str, input: &str, input_format: Option<&str>) -> jsonrpc::Result<Option<serde_json::Value>> {
    let mut engine = mq_lang::DefaultEngine::default();
    let input =
        match input_format.unwrap_or("markdown") {
            "markdown" => mq_lang::parse_markdown_input(input)
                .unwrap_or_else(|_| vec![mq_lang::RuntimeValue::String(input.to_string())]),
            "mdx" => mq_lang::parse_mdx_input(input)
                .unwrap_or_else(|_| vec![mq_lang::RuntimeValue::String(input.to_string())]),
            "html" => mq_lang::parse_html_input(input)
                .unwrap_or_else(|_| vec![mq_lang::RuntimeValue::String(input.to_string())]),
            "text" => mq_lang::parse_text_input(input)
                .unwrap_or_else(|_| vec![mq_lang::RuntimeValue::String(input.to_string())]),
            _ => {
                return Err(jsonrpc::Error {
                    code: jsonrpc::ErrorCode::InvalidParams,
                    message: Cow::Owned(format!(
                        "Unsupported input format: {}",
                        input_format.unwrap_or("unknown")
                    )),
                    data: None,
                });
            }
        };

    engine.load_builtin_module();
    let result = engine.eval(code, input.into_iter());

    match result {
        Ok(values) => {
            let markdown = mq_markdown::Markdown::new(
                values
                    .into_iter()
                    .map(|value| match value {
                        mq_lang::RuntimeValue::Markdown(node, _) => node.clone(),
                        _ => value.to_string().into(),
                    })
                    .collect(),
            );

            Ok(Some(markdown.to_string().into()))
        }
        Err(e) => Err(jsonrpc::Error {
            code: jsonrpc::ErrorCode::InternalError,
            message: Cow::Owned(format!("Error: {}", e)),
            data: None,
        }),
    }
}

#[cfg(test)]
mod tests {
    use serde_json::Value;
    use tower_lsp_server::ls_types::ExecuteCommandParams;

    use super::*;

    #[test]
    fn test_run_with_valid_text() {
        let input = "# Test\nThis is a test".to_string();
        let params = ExecuteCommandParams {
            command: "mq/run".to_string(),
            arguments: vec![Value::String("add(1, 2)".to_string()), input.into()],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_ok());
    }
    #[test]
    fn test_no_arguments() {
        let params = ExecuteCommandParams {
            command: "mq/run".to_string(),
            arguments: vec![],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert_eq!(e.message, "No arguments provided");
        }
    }

    #[test]
    fn test_invalid_command() {
        let params = ExecuteCommandParams {
            command: "mq/invalid".to_string(),
            arguments: vec![Value::String("query".to_string()), Value::String("input".to_string())],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert_eq!(e.message, "Invalid arguments");
        }
    }

    #[test]
    fn test_run_with_insufficient_arguments() {
        let params = ExecuteCommandParams {
            command: "mq/run".to_string(),
            arguments: vec![Value::String("query".to_string())],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert_eq!(e.message, "Invalid arguments");
        }
    }

    #[test]
    fn test_run_with_invalid_query() {
        let input = "# Test\nThis is a test".to_string();
        let params = ExecuteCommandParams {
            command: "mq/run".to_string(),
            arguments: vec![Value::String("invalid_function()".to_string()), input.into()],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InternalError);
            assert!(e.message.contains("Error:"));
        }
    }

    #[test]
    fn test_to_ast_json_with_valid_code() {
        let code = "add(1, 2)";
        let params = ExecuteCommandParams {
            command: "mq/to_ast_json".to_string(),
            arguments: vec![Value::String(code.to_string())],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_ok());
        let value = response.unwrap();
        assert!(value.is_some());
        let json = value.unwrap();
        assert!(json.to_string().contains("expr"));
    }

    #[test]
    fn test_to_ast_json_with_invalid_code() {
        let code = "add(";
        let params = ExecuteCommandParams {
            command: "mq/to_ast_json".to_string(),
            arguments: vec![Value::String(code.to_string())],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert!(e.message.contains("Error:"));
        }
    }

    #[test]
    fn test_to_ast_json_with_no_arguments() {
        let params = ExecuteCommandParams {
            command: "mq/to_ast_json".to_string(),
            arguments: vec![],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert_eq!(e.message, "No arguments provided");
        }
    }

    #[test]
    fn test_to_ast_json_with_extra_arguments() {
        let params = ExecuteCommandParams {
            command: "mq/to_ast_json".to_string(),
            arguments: vec![
                Value::String("add(1, 2)".to_string()),
                Value::String("extra".to_string()),
            ],
            work_done_progress_params: Default::default(),
        };

        let response = response(params);
        assert!(response.is_err());
        if let Err(e) = response {
            assert_eq!(e.code, jsonrpc::ErrorCode::InvalidParams);
            assert_eq!(e.message, "Invalid arguments");
        }
    }
}