1use alloc::{string::ToString, vec::Vec};
2
3use miden_objects::{
4 NoteError,
5 account::{Account, AccountId},
6 note::{NoteExecutionMode, NoteId, NoteTag},
7};
8use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
9use tracing::warn;
10
11use crate::{
12 Client,
13 errors::ClientError,
14 store::{InputNoteRecord, NoteRecordError},
15};
16
17impl Client {
19 pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
32 self.store.get_note_tags().await.map_err(Into::into)
33 }
34
35 pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
37 match self
38 .store
39 .add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
40 .await
41 .map_err(Into::into)
42 {
43 Ok(true) => Ok(()),
44 Ok(false) => {
45 warn!("Tag {} is already being tracked", tag);
46 Ok(())
47 },
48 Err(err) => Err(err),
49 }
50 }
51
52 pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
54 if self
55 .store
56 .remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
57 .await?
58 == 0
59 {
60 warn!("Tag {} wasn't being tracked", tag);
61 }
62
63 Ok(())
64 }
65}
66
67#[derive(Debug, PartialEq, Eq, Clone, Copy)]
69pub struct NoteTagRecord {
70 pub tag: NoteTag,
71 pub source: NoteTagSource,
72}
73
74#[derive(Debug, PartialEq, Eq, Clone, Copy)]
77pub enum NoteTagSource {
78 Account(AccountId),
80 Note(NoteId),
82 User,
84}
85
86impl NoteTagRecord {
87 pub fn with_note_source(tag: NoteTag, note_id: NoteId) -> Self {
88 Self {
89 tag,
90 source: NoteTagSource::Note(note_id),
91 }
92 }
93
94 pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
95 Self {
96 tag,
97 source: NoteTagSource::Account(account_id),
98 }
99 }
100}
101
102impl Serializable for NoteTagSource {
103 fn write_into<W: ByteWriter>(&self, target: &mut W) {
104 match self {
105 NoteTagSource::Account(account_id) => {
106 target.write_u8(0);
107 account_id.write_into(target);
108 },
109 NoteTagSource::Note(note_id) => {
110 target.write_u8(1);
111 note_id.write_into(target);
112 },
113 NoteTagSource::User => target.write_u8(2),
114 }
115 }
116}
117
118impl Deserializable for NoteTagSource {
119 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
120 match source.read_u8()? {
121 0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
122 1 => Ok(NoteTagSource::Note(NoteId::read_from(source)?)),
123 2 => Ok(NoteTagSource::User),
124 val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
125 }
126 }
127}
128
129impl PartialEq<NoteTag> for NoteTagRecord {
130 fn eq(&self, other: &NoteTag) -> bool {
131 self.tag == *other
132 }
133}
134
135impl TryInto<NoteTagRecord> for &InputNoteRecord {
136 type Error = NoteRecordError;
137
138 fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
139 match self.metadata() {
140 Some(metadata) => Ok(NoteTagRecord::with_note_source(metadata.tag(), self.id())),
141 None => Err(NoteRecordError::ConversionError(
142 "Input Note Record does not contain tag".to_string(),
143 )),
144 }
145 }
146}
147
148impl TryInto<NoteTagRecord> for &Account {
149 type Error = NoteError;
150 fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
151 Ok(NoteTagRecord::with_account_source(
152 NoteTag::from_account_id(self.id(), NoteExecutionMode::Local)?,
153 self.id(),
154 ))
155 }
156}