1use std::str::FromStr;
2
3use markdown::{mdast::Node, to_mdast, ParseOptions};
4use serde::{Deserialize, Serialize};
5
6use crate::core::application::file::{Client, DataType, Datacap, DatacapGroup, Project};
7
8#[derive(Serialize, Deserialize, Debug)]
9pub enum ParsedApplicationDataFields {
10 Version,
11 Address,
12 Name,
14 Region,
15 Industry,
16 Website,
17 SocialMedia,
18 SocialMediaType,
19 Role,
20 ProjectID,
22 ProjectBriefHistory,
23 AssociatedProjects,
24 DataDesc,
25 DataSrc,
26 DataPrepare,
27 DataSampleLink,
28 ConfirmPublicDataset,
29 RetrivalFreq,
30 DataLifeSpan,
31 DataGeographies,
32 DataDistribution,
33 ProviderIDs,
34 DatacapGroup,
36 Type,
37 TotalRequestedAmount,
38 SingleSizeDataset,
39 Replicas,
40 WeeklyAllocation,
41 InvalidField,
42}
43
44impl From<String> for ParsedApplicationDataFields {
45 fn from(s: String) -> Self {
46 match s.as_str() {
47 "Version" => ParsedApplicationDataFields::Version,
48 "On Chain Address" => ParsedApplicationDataFields::Address,
49 "Name" => ParsedApplicationDataFields::Name,
51 "Region" => ParsedApplicationDataFields::Region,
52 "Industry" => ParsedApplicationDataFields::Industry,
53 "Website" => ParsedApplicationDataFields::Website,
54 "Social Media" => ParsedApplicationDataFields::SocialMedia,
55 "Social Media Type" => ParsedApplicationDataFields::SocialMediaType,
56 "Role" => ParsedApplicationDataFields::Role,
57 "Project ID" => ParsedApplicationDataFields::ProjectID,
59 "Brief history of your project and organization" => {
60 ParsedApplicationDataFields::ProjectBriefHistory
61 }
62 "Is this project associated with other projects/ecosystem stakeholders?" => {
63 ParsedApplicationDataFields::AssociatedProjects
64 }
65 "Describe the data being stored onto Filecoin" => {
66 ParsedApplicationDataFields::DataDesc
67 },
68 "Where was the data currently stored in this dataset sourced from"=> {
69 ParsedApplicationDataFields::DataSrc
70 },
71 "How do you plan to prepare the dataset" => {
72 ParsedApplicationDataFields::DataPrepare
73 },
74 "Please share a sample of the data (a link to a file, an image, a table, etc., are good ways to do this." => {
75 ParsedApplicationDataFields::DataSampleLink
76 },
77 "Confirm that this is a public dataset that can be retrieved by anyone on the network (i.e., no specific permissions or access rights are required to view the data)" => {
78 ParsedApplicationDataFields::ConfirmPublicDataset
79 },
80 "What is the expected retrieval frequency for this data" => {
81 ParsedApplicationDataFields::RetrivalFreq
82 },
83 "For how long do you plan to keep this dataset stored on Filecoin" => {
84 ParsedApplicationDataFields::DataLifeSpan
85 },
86 "In which geographies do you plan on making storage deals" => {
87 ParsedApplicationDataFields::DataGeographies
88 },
89 "How will you be distributing your data to storage providers" => {
90 ParsedApplicationDataFields::DataDistribution
91 },
92 "Please list the provider IDs and location of the storage providers you will be working with. Note that it is a requirement to list a minimum of 5 unique provider IDs, and that your client address will be verified against this list in the future" => {
93 ParsedApplicationDataFields::ProviderIDs
94 },
95 "Group" => ParsedApplicationDataFields::DatacapGroup,
97 "Type" => ParsedApplicationDataFields::Type,
98 "Total Requested Amount" => ParsedApplicationDataFields::TotalRequestedAmount,
99 "Single Size Dataset" => ParsedApplicationDataFields::SingleSizeDataset,
100 "Replicas" => ParsedApplicationDataFields::Replicas,
101 "Weekly Allocation" => ParsedApplicationDataFields::WeeklyAllocation,
102 _ => ParsedApplicationDataFields::InvalidField,
104 }
105 }
106}
107
108#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
109pub struct ParsedIssue {
110 pub version: u8,
111 pub id: String,
112 pub client: Client,
113 pub project: Project,
114 pub datacap: Datacap,
115}
116
117impl ParsedIssue {
118 pub fn from_issue_body(body: &str) -> Self {
119 let tree: Node = to_mdast(body, &ParseOptions::default()).unwrap();
120 let mut data: IssueValidData = IssueValidData::default();
121 for (index, i) in tree.children().unwrap().into_iter().enumerate().step_by(2) {
122 let prop = i.to_string();
123 let tree = tree.children().unwrap().into_iter();
124 let value = match tree.skip(index + 1).next() {
125 Some(v) => v.to_string(),
126 None => continue,
127 };
128 match prop.clone().into() {
129 ParsedApplicationDataFields::InvalidField => {
130 continue;
131 }
132 _ => data.0.push((Prop(prop), Value(value))),
133 }
134 }
135 let client = Client::from(data.clone());
136 let project = Project::from(data.clone());
137 let datacap = Datacap::from(data.clone());
138 let version = data
139 .clone()
140 .0
141 .into_iter()
142 .find(|(prop, _)| prop.0 == "Version")
143 .unwrap()
144 .1
145 .0
146 .parse::<u8>()
147 .unwrap();
148 let id = data
149 .0
150 .into_iter()
151 .find(|(prop, _)| prop.0 == "On Chain Address")
152 .unwrap()
153 .1
154 .0;
155
156 Self {
157 id,
158 version,
159 client,
160 project,
161 datacap,
162 }
163 }
164}
165
166#[derive(Debug, Clone, Default)]
167pub struct Prop(pub String);
168#[derive(Debug, Clone, Default)]
169pub struct Value(pub String);
170
171#[derive(Debug, Clone, Default)]
172pub struct IssueValidData(pub Vec<(Prop, Value)>);
173
174impl From<IssueValidData> for Project {
175 fn from(data: IssueValidData) -> Self {
176 let mut project = Project::default();
177 for (prop, value) in data.0 {
178 match prop.0.into() {
179 ParsedApplicationDataFields::ProjectID => {
180 project.project_id = value.0;
181 }
182 ParsedApplicationDataFields::ProjectBriefHistory => {
183 project.history = value.0;
184 }
185 ParsedApplicationDataFields::AssociatedProjects => {
186 project.associated_projects = value.0;
187 }
188 ParsedApplicationDataFields::DataDesc => {
189 project.stored_data_desc = value.0;
190 }
191 ParsedApplicationDataFields::DataSrc => {
192 project.previous_stoarge = value.0;
193 }
194 ParsedApplicationDataFields::DataPrepare => {
195 project.dataset_prepare = value.0;
196 }
197 ParsedApplicationDataFields::DataSampleLink => {
198 project.data_sample_link = value.0;
199 }
200 ParsedApplicationDataFields::ConfirmPublicDataset => {
201 project.public_dataset = value.0;
202 }
203 ParsedApplicationDataFields::RetrivalFreq => {
204 project.retrival_frequency = value.0;
205 }
206 ParsedApplicationDataFields::DataLifeSpan => {
207 project.dataset_life_span = value.0;
208 }
209 ParsedApplicationDataFields::DataGeographies => {
210 project.geographis = value.0;
211 }
212 ParsedApplicationDataFields::DataDistribution => {
213 project.distribution = value.0;
214 }
215 ParsedApplicationDataFields::ProviderIDs => {
216 project.providers = value.0;
217 }
218 _ => {}
219 }
220 }
221 project
222 }
223}
224
225impl From<IssueValidData> for Client {
226 fn from(data: IssueValidData) -> Self {
227 let mut client = Client::default();
228 for (prop, value) in data.0 {
229 match prop.0.into() {
230 ParsedApplicationDataFields::Name => {
231 client.name = value.0;
232 }
233 ParsedApplicationDataFields::Region => {
234 client.region = value.0;
235 }
236 ParsedApplicationDataFields::Industry => {
237 client.industry = value.0;
238 }
239 ParsedApplicationDataFields::Website => {
240 client.website = value.0;
241 }
242 ParsedApplicationDataFields::SocialMedia => {
243 client.social_media = value.0;
244 }
245 ParsedApplicationDataFields::SocialMediaType => {
246 client.social_media_type = value.0;
247 }
248 ParsedApplicationDataFields::Role => {
249 client.role = value.0;
250 }
251 _ => {}
252 }
253 }
254 client
255 }
256}
257
258impl From<IssueValidData> for Datacap {
259 fn from(data: IssueValidData) -> Self {
260 let mut datacap = Datacap::default();
261 for (prop, value) in data.0 {
262 match prop.0.into() {
263 ParsedApplicationDataFields::DatacapGroup => {
264 datacap._group = DatacapGroup::from_str(&value.0).unwrap();
265 }
266 ParsedApplicationDataFields::Type => {
267 datacap.data_type = DataType::from_str(&value.0).unwrap();
268 }
269 ParsedApplicationDataFields::TotalRequestedAmount => {
270 datacap.total_requested_amount = value.0;
271 }
272 ParsedApplicationDataFields::SingleSizeDataset => {
273 datacap.single_size_dataset = value.0;
274 }
275 ParsedApplicationDataFields::Replicas => {
276 datacap.replicas = value.0.parse::<u8>().unwrap();
277 }
278 ParsedApplicationDataFields::WeeklyAllocation => {
279 datacap.weekly_allocation = value.0;
280 }
281 _ => {}
282 }
283 }
284 datacap
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use crate::external_services::github::GithubWrapper;
291
292 #[tokio::test]
293 async fn test_parser() {
294 let gh = GithubWrapper::new();
295 let issue = gh.list_issue(471).await.unwrap();
296 let parsed_ldn = super::ParsedIssue::from_issue_body(&issue.body.unwrap());
297
298 assert_eq!(parsed_ldn.version, 1);
299 assert!(!parsed_ldn.id.is_empty());
300
301 assert!(!parsed_ldn.client.name.is_empty());
302 assert!(!parsed_ldn.client.industry.is_empty());
303 assert!(!parsed_ldn.client.region.is_empty());
304 assert!(!parsed_ldn.client.website.is_empty());
305 assert!(!parsed_ldn.client.social_media.is_empty());
306 assert!(!parsed_ldn.client.social_media_type.is_empty());
307 assert!(!parsed_ldn.client.role.is_empty());
308 assert!(!parsed_ldn.project.project_id.is_empty());
309 assert!(!parsed_ldn.project.history.is_empty());
310 assert!(!parsed_ldn.project.associated_projects.is_empty());
311
312 assert!(!parsed_ldn.datacap.total_requested_amount.is_empty());
313 }
314}