1mod format;
8mod query;
9mod stream;
10
11use anyhow::{Context, Result};
12use clap::{Parser, ValueEnum};
13
14use crate::request_log;
15use query::Filter;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
19#[value(rename_all = "lowercase")]
20pub enum Format {
21 Oneline,
23 Json,
25 Full,
27}
28
29#[derive(Parser)]
31pub struct LogCommand {
32 #[arg(long, value_name = "DUR")]
34 since: Option<String>,
35 #[arg(long, value_name = "METHOD")]
37 method: Option<String>,
38 #[arg(long, value_name = "STATUS")]
40 status: Option<String>,
41 #[arg(long, value_name = "NAME")]
43 service: Option<String>,
44 #[arg(long, value_name = "PATH")]
46 command: Option<String>,
47 #[arg(long, value_name = "SUBSTR")]
49 url: Option<String>,
50 #[arg(long, value_name = "REGEX")]
52 grep: Option<String>,
53 #[arg(long, value_name = "TOKEN")]
55 fuzzy: Vec<String>,
56 #[arg(long, value_name = "EXPR")]
59 query: Vec<String>,
60 #[arg(long, value_name = "ID")]
62 id: Option<String>,
63 #[arg(long, value_enum, default_value_t = Format::Oneline)]
65 format: Format,
66 #[arg(short = 'n', long, value_name = "N")]
68 limit: Option<usize>,
69 #[arg(short = 'f', long)]
71 follow: bool,
72}
73
74impl LogCommand {
75 pub fn execute(self) -> Result<()> {
77 let path = request_log::log_file_path().context("could not resolve the log file path")?;
78 let filter = Filter::build(query::FilterInput {
79 since: self.since.as_deref(),
80 method: self.method.as_deref(),
81 status: self.status.as_deref(),
82 service: self.service.as_deref(),
83 command: self.command.as_deref(),
84 url: self.url.as_deref(),
85 grep: self.grep.as_deref(),
86 fuzzy: &self.fuzzy,
87 query: &self.query,
88 id: self.id.as_deref(),
89 })?;
90 stream::run(&path, &filter, self.format, self.limit, self.follow)
91 }
92}
93
94#[cfg(test)]
95#[allow(clippy::unwrap_used, clippy::expect_used)]
96mod tests {
97 use super::*;
98
99 #[derive(Parser)]
100 struct Wrapper {
101 #[command(subcommand)]
102 cmd: Wrapped,
103 }
104
105 #[derive(clap::Subcommand)]
106 enum Wrapped {
107 Log(LogCommand),
108 }
109
110 fn parse(args: &[&str]) -> LogCommand {
111 let mut full = vec!["omni-dev", "log"];
112 full.extend_from_slice(args);
113 match Wrapper::try_parse_from(full).unwrap().cmd {
114 Wrapped::Log(cmd) => cmd,
115 }
116 }
117
118 #[test]
119 fn defaults_are_sane() {
120 let cmd = parse(&[]);
121 assert_eq!(cmd.format, Format::Oneline);
122 assert!(cmd.limit.is_none());
123 assert!(!cmd.follow);
124 }
125
126 #[test]
127 fn parses_full_flag_matrix() {
128 let cmd = parse(&[
129 "--since",
130 "2h",
131 "--method",
132 "GET",
133 "--status",
134 "5xx",
135 "--service",
136 "jira",
137 "--command",
138 "jira read",
139 "--url",
140 "issue",
141 "--grep",
142 "X-\\d+",
143 "--fuzzy",
144 "a",
145 "--fuzzy",
146 "b",
147 "--query",
148 "status:5xx OR method:POST",
149 "--id",
150 "abc",
151 "--format",
152 "json",
153 "-n",
154 "10",
155 "-f",
156 ]);
157 assert_eq!(cmd.since.as_deref(), Some("2h"));
158 assert_eq!(cmd.status.as_deref(), Some("5xx"));
159 assert_eq!(cmd.fuzzy, vec!["a", "b"]);
160 assert_eq!(cmd.query.len(), 1);
161 assert_eq!(cmd.format, Format::Json);
162 assert_eq!(cmd.limit, Some(10));
163 assert!(cmd.follow);
164 }
165}