1use serde::{Deserialize, Serialize};
29use thiserror::Error;
30
31use crate::event::Kind;
32use crate::key::{PublicKey, PublicKeyError};
33use crate::types::{Url, UrlError};
34
35pub const CONTENT_TYPE: &str = "application/nostr+json+rpc";
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40pub struct Request {
41 pub method: String,
43 #[serde(default)]
45 pub params: Vec<serde_json::Value>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50pub struct Response {
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub result: Option<serde_json::Value>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub error: Option<String>,
57}
58
59impl Response {
60 #[must_use]
62 pub const fn is_error(&self) -> bool {
63 self.error.is_some()
64 }
65
66 #[must_use]
68 pub const fn ok(result: serde_json::Value) -> Self {
69 Self {
70 result: Some(result),
71 error: None,
72 }
73 }
74
75 #[must_use]
77 pub fn err(message: impl Into<String>) -> Self {
78 Self {
79 result: None,
80 error: Some(message.into()),
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87pub enum Method {
88 SupportedMethods,
90 BanPubkey,
92 UnbanPubkey,
94 ListBannedPubkeys,
96 AllowPubkey,
98 UnallowPubkey,
100 ListAllowedPubkeys,
102 ListEventsNeedingModeration,
104 AllowEvent,
106 BanEvent,
108 ListBannedEvents,
110 ChangeRelayName,
112 ChangeRelayDescription,
114 ChangeRelayIcon,
116 AllowKind,
118 DisallowKind,
120 ListAllowedKinds,
122 BlockIp,
124 UnblockIp,
126 ListBlockedIps,
128 Custom(String),
130}
131
132impl Method {
133 #[must_use]
135 #[expect(
136 clippy::missing_const_for_fn,
137 reason = "`Self::Custom` borrows from a heap `String`"
138 )]
139 pub fn as_str(&self) -> &str {
140 match self {
141 Self::SupportedMethods => "supportedmethods",
142 Self::BanPubkey => "banpubkey",
143 Self::UnbanPubkey => "unbanpubkey",
144 Self::ListBannedPubkeys => "listbannedpubkeys",
145 Self::AllowPubkey => "allowpubkey",
146 Self::UnallowPubkey => "unallowpubkey",
147 Self::ListAllowedPubkeys => "listallowedpubkeys",
148 Self::ListEventsNeedingModeration => "listeventsneedingmoderation",
149 Self::AllowEvent => "allowevent",
150 Self::BanEvent => "banevent",
151 Self::ListBannedEvents => "listbannedevents",
152 Self::ChangeRelayName => "changerelayname",
153 Self::ChangeRelayDescription => "changerelaydescription",
154 Self::ChangeRelayIcon => "changerelayicon",
155 Self::AllowKind => "allowkind",
156 Self::DisallowKind => "disallowkind",
157 Self::ListAllowedKinds => "listallowedkinds",
158 Self::BlockIp => "blockip",
159 Self::UnblockIp => "unblockip",
160 Self::ListBlockedIps => "listblockedips",
161 Self::Custom(s) => s.as_str(),
162 }
163 }
164
165 #[must_use]
167 pub fn parse(token: &str) -> Self {
168 match token {
169 "supportedmethods" => Self::SupportedMethods,
170 "banpubkey" => Self::BanPubkey,
171 "unbanpubkey" => Self::UnbanPubkey,
172 "listbannedpubkeys" => Self::ListBannedPubkeys,
173 "allowpubkey" => Self::AllowPubkey,
174 "unallowpubkey" => Self::UnallowPubkey,
175 "listallowedpubkeys" => Self::ListAllowedPubkeys,
176 "listeventsneedingmoderation" => Self::ListEventsNeedingModeration,
177 "allowevent" => Self::AllowEvent,
178 "banevent" => Self::BanEvent,
179 "listbannedevents" => Self::ListBannedEvents,
180 "changerelayname" => Self::ChangeRelayName,
181 "changerelaydescription" => Self::ChangeRelayDescription,
182 "changerelayicon" => Self::ChangeRelayIcon,
183 "allowkind" => Self::AllowKind,
184 "disallowkind" => Self::DisallowKind,
185 "listallowedkinds" => Self::ListAllowedKinds,
186 "blockip" => Self::BlockIp,
187 "unblockip" => Self::UnblockIp,
188 "listblockedips" => Self::ListBlockedIps,
189 _ => Self::Custom(token.to_owned()),
190 }
191 }
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196pub struct PubkeyEntry {
197 pub pubkey: String,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub reason: Option<String>,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
207pub struct EventEntry {
208 pub id: String,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub reason: Option<String>,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
217pub struct IpEntry {
218 pub ip: String,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub reason: Option<String>,
223}
224
225#[derive(Debug, Error)]
227#[non_exhaustive]
228pub enum ManagementError {
229 #[error(transparent)]
231 Json(#[from] serde_json::Error),
232 #[error(transparent)]
234 InvalidPublicKey(#[from] PublicKeyError),
235 #[error(transparent)]
237 InvalidUrl(#[from] UrlError),
238}
239
240#[must_use]
242pub fn pubkey_request(method: &Method, pubkey: &PublicKey, reason: Option<&str>) -> Request {
243 let mut params = vec![serde_json::Value::String(pubkey.to_hex())];
244 if let Some(r) = reason {
245 params.push(serde_json::Value::String(r.to_owned()));
246 }
247 Request {
248 method: method.as_str().to_owned(),
249 params,
250 }
251}
252
253#[must_use]
256pub fn event_request(method: &Method, event_id_hex: &str, reason: Option<&str>) -> Request {
257 let mut params = vec![serde_json::Value::String(event_id_hex.to_owned())];
258 if let Some(r) = reason {
259 params.push(serde_json::Value::String(r.to_owned()));
260 }
261 Request {
262 method: method.as_str().to_owned(),
263 params,
264 }
265}
266
267#[must_use]
270pub fn kind_request(method: &Method, kind: Kind) -> Request {
271 Request {
272 method: method.as_str().to_owned(),
273 params: vec![serde_json::Value::Number(kind.as_u16().into())],
274 }
275}
276
277#[must_use]
279pub fn ip_request(method: &Method, ip: &str, reason: Option<&str>) -> Request {
280 let mut params = vec![serde_json::Value::String(ip.to_owned())];
281 if let Some(r) = reason {
282 params.push(serde_json::Value::String(r.to_owned()));
283 }
284 Request {
285 method: method.as_str().to_owned(),
286 params,
287 }
288}
289
290#[must_use]
293pub fn string_request(method: &Method, value: impl Into<String>) -> Request {
294 Request {
295 method: method.as_str().to_owned(),
296 params: vec![serde_json::Value::String(value.into())],
297 }
298}
299
300#[must_use]
302pub fn url_request(method: &Method, url: &Url) -> Request {
303 string_request(method, url.as_str())
304}
305
306#[must_use]
308pub fn empty_request(method: &Method) -> Request {
309 Request {
310 method: method.as_str().to_owned(),
311 params: Vec::new(),
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::Keys;
319
320 fn keys() -> Keys {
321 Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
322 }
323
324 #[test]
325 fn ban_pubkey_request_roundtrip() {
326 let req = pubkey_request(&Method::BanPubkey, keys().public_key(), Some("spam"));
327 let json = serde_json::to_string(&req).unwrap();
328 assert!(json.contains("banpubkey"));
329 assert!(json.contains("spam"));
330 let parsed: Request = serde_json::from_str(&json).unwrap();
331 assert_eq!(parsed.method, "banpubkey");
332 assert_eq!(parsed.params.len(), 2);
333 }
334
335 #[test]
336 fn response_serialisation() {
337 let resp = Response::ok(serde_json::json!(true));
338 let json = serde_json::to_string(&resp).unwrap();
339 assert_eq!(json, r#"{"result":true}"#);
340 let err = Response::err("forbidden");
341 let json2 = serde_json::to_string(&err).unwrap();
342 assert!(json2.contains("forbidden"));
343 }
344
345 #[test]
346 fn method_round_trip_custom() {
347 assert_eq!(
348 Method::parse("nostr.relay.custom"),
349 Method::Custom("nostr.relay.custom".to_owned())
350 );
351 }
352
353 #[test]
354 fn pubkey_entry_roundtrip() {
355 let entry = PubkeyEntry {
356 pubkey: keys().public_key().to_hex(),
357 reason: Some("abuse".into()),
358 };
359 let json = serde_json::to_string(&entry).unwrap();
360 let parsed: PubkeyEntry = serde_json::from_str(&json).unwrap();
361 assert_eq!(parsed, entry);
362 }
363
364 #[test]
365 fn empty_request_has_no_params() {
366 let req = empty_request(&Method::SupportedMethods);
367 assert!(req.params.is_empty());
368 }
369}