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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::any::Any;
use std::collections::HashMap;
use std::env::args;
use std::sync::Arc;
use dce_router::protocol::{CustomizedProtocolRawRequest, RoutableProtocol};
use dce_router::request::{RawRequest, Request, RequestContext};
use dce_router::router::Router;
use dce_router::serializer::Serialized;
use dce_util::mixed::DceResult;
#[cfg(feature = "async")]
use async_trait::async_trait;


pub type CliRaw = Request<CustomizedProtocolRawRequest<CliProtocol>, (), (), (), ()>;
pub type CliRequest<Resp> = Request<CustomizedProtocolRawRequest<CliProtocol>, (), (), Resp, Resp>;
pub type CliConvert<Resp, RespDto> = Request<CustomizedProtocolRawRequest<CliProtocol>, (), (), Resp, RespDto>;

const PASS_SEPARATOR: &str = "--";

enum ArgType {
    AssignExpr(String, String), // arg is an assign expr, 0th is name, 1st is value
    PrefixName, // - prefix name
    DownFlowSeparator, // a separator to separate remain args to down flow cli
    Path, // path type arg
}

#[derive(Debug, Default)]
pub struct CliProtocol {
    raw: Vec<String>,
    path: String,
    pass: Vec<String>,
    args: HashMap<String, String>,
}

impl CliProtocol {
    pub fn raw(&self) -> &Vec<String> {
        &self.raw
    }

    pub fn pass(&self) -> &Vec<String> {
        &self.pass
    }

    pub fn args(&self) -> &HashMap<String, String> {
        &self.args
    }

    pub fn args_mut(&mut self) -> &mut HashMap<String, String> {
        &mut self.args
    }

    #[cfg(feature = "async")]
    pub async fn route(self, router: Arc<Router<CustomizedProtocolRawRequest<Self>>>, context_data: HashMap<String, Box<dyn Any + Send>>) {
        if let Some(resp) = CliProtocol::default().handle_result(CustomizedProtocolRawRequest::route(
            RequestContext::new(router, CustomizedProtocolRawRequest::new(self)).set_data(context_data)
        ).await) {
            println!("{resp}");
        }
    }

    #[cfg(not(feature = "async"))]
    pub fn route(self, router: Arc<Router<CustomizedProtocolRawRequest<Self>>>, context_data: HashMap<String, Box<dyn Any + Send>>) {
        if let Some(resp) = CliProtocol::default().handle_result(CustomizedProtocolRawRequest::route(
            RequestContext::new(router, CustomizedProtocolRawRequest::new(self)).set_data(context_data)
        )) {
            println!("{resp}");
        }
    }

    pub fn new(base: usize) -> Self {
        let raw = args().collect::<Vec<_>>();
        let mut cli = Self::from(raw.iter().skip(base).map(|a| a.clone()).collect::<Vec<_>>());
        cli.raw = raw;
        cli
    }

    fn parse_type(arg: &str) -> ArgType {
        return if let Some((left, right)) = arg.split_once("=") {
            ArgType::AssignExpr(left.to_string(), right.to_string())
        } else if arg.starts_with("-") {
            if arg == PASS_SEPARATOR { return ArgType::DownFlowSeparator }
            ArgType::PrefixName
        } else {
            ArgType::Path
        }
    }
}

impl From<Vec<String>> for CliProtocol {
    fn from(mut value: Vec<String>) -> Self {
        let mut pass = vec![];
        let mut paths = vec![];
        let mut args = HashMap::<String, String>::new();

        while ! value.is_empty() {
            let arg = value.remove(0);
            match Self::parse_type(&arg) {
                ArgType::AssignExpr(name, value) => { args.insert(name, value); },
                ArgType::PrefixName => {
                    args.insert(arg, match value.get(0) {
                        Some(next) if matches!(Self::parse_type(next), ArgType::Path) => value.remove(0),
                        _ => String::new(),
                    });
                },
                ArgType::DownFlowSeparator => {
                    while ! value.is_empty() { pass.push(value.remove(0)); }
                },
                _ => { paths.push(arg); },
            }
        }

        CliProtocol { raw: vec![], path: paths.join("/"), pass, args }
    }
}

impl Into<String> for CliProtocol {
    fn into(self) -> String {
        unreachable!()
    }
}

#[cfg_attr(feature = "async", async_trait)]
impl RoutableProtocol for CliProtocol {
    type Req = Vec<String>;
    type Resp = String;

    fn path(&self) -> &str {
        &self.path
    }

    #[cfg(feature = "async")]
    async fn body(&mut self) -> DceResult<Serialized> {
        unreachable!("not support cli body yet")
    }

    #[cfg(not(feature = "async"))]
    fn body(&mut self) -> DceResult<Serialized> {
        unreachable!("not support cli body yet")
    }

    fn pack_resp(self, serialized: Serialized) -> Self::Resp {
        match serialized {
            Serialized::String(str) => str,
            Serialized::Bytes(bytes) => String::from_utf8_lossy(bytes.as_ref()).to_string(),
        }
    }

    fn handle_result(self, (unresponsive, response): (Option<bool>, DceResult<Option<Self::Resp>>)) -> Option<String>{
        Self::try_print_err(&response);
        if let Ok(Some(resp)) = if unresponsive.unwrap_or(true) { Ok(None) } else { response } {
            return Some(resp);
        }
        None
    }
}