shodan_client/
builders.rs1use std::collections::HashMap;
2
3#[derive(Default)]
4pub struct SslFilterBuilder {
5 filters: HashMap<String, Vec<String>>,
6}
7
8impl SslFilterBuilder {
9 pub fn build(self) -> HashMap<String, Vec<String>> {
10 self.filters
11 }
12
13 pub fn cert_subject_cn(mut self, value: impl ToString) -> Self {
14 let values = self.filters.entry("cert.subject.cn".into()).or_default();
15
16 values.push(value.to_string());
17
18 self
19 }
20}
21
22#[derive(Default)]
23pub struct SearchQueryBuilder {
24 query: String,
25 filters: HashMap<String, Vec<String>>,
26}
27
28impl SearchQueryBuilder {
29 pub fn build(self) -> String {
30 let mut query = vec![];
31 if !self.query.is_empty() {
32 query.push(self.query);
33 }
34
35 for (filter, values) in self.filters {
36 query.push(format!("{}:{}", filter, values.join(",")));
37 }
38
39 query.join(" ")
40 }
41
42 pub fn query(mut self, query: impl ToString) -> Self {
43 self.query = query.to_string();
44
45 self
46 }
47
48 pub fn port(mut self, value: impl ToString) -> Self {
49 let values = self.filters.entry("port".into()).or_default();
50
51 values.push(value.to_string());
52
53 self
54 }
55
56 pub fn product(mut self, filter: impl ToString) -> Self {
57 let values = self.filters.entry("product".into()).or_default();
58
59 values.push(filter.to_string());
60
61 self
62 }
63
64 pub fn ssl(mut self, closure: fn(SslFilterBuilder) -> SslFilterBuilder) -> Self {
65 let filters = closure(SslFilterBuilder::default()).build();
66
67 self.filters.extend(filters);
68
69 self
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use crate::*;
76
77 #[test]
78 fn can_build_query() {
79 let query = SearchQueryBuilder::default()
80 .query("apache")
81 .port("69")
82 .port(420)
83 .product("Apache")
84 .product("Ngnix")
85 .build();
86
87 assert!(query.contains("port:69,420"), "query was: {query}");
88 assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
89 assert!(query.starts_with("apache"), "query was: {query}");
90 }
91
92 #[test]
93 fn can_build_without_query() {
94 let query = SearchQueryBuilder::default()
95 .port("69")
96 .port(420)
97 .product("Apache")
98 .product("Ngnix")
99 .build();
100
101 assert!(query.contains("port:69,420"), "query was: {query}");
102 assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
103 }
104
105 #[test]
106 fn can_build_with_ssl() {
107 let query = SearchQueryBuilder::default()
108 .port("69")
109 .port(420)
110 .product("Apache")
111 .product("Ngnix")
112 .ssl(|ssl_builder| {
113 ssl_builder
114 .cert_subject_cn("google.com")
115 .cert_subject_cn("bing.com")
116 })
117 .build();
118
119 assert!(query.contains("port:69,420"), "query was: {query}");
120 assert!(query.contains("product:Apache,Ngnix"), "query was: {query}");
121 assert!(
122 query.contains("cert.subject.cn:google.com,bing.com"),
123 "query was: {query}"
124 );
125 }
126}