json_resume/
lib.rs

1#![allow(rustdoc::private_intra_doc_links)]
2#![deny(
3    // Documentation
4	// TODO: rustdoc::broken_intra_doc_links,
5	// TODO: rustdoc::missing_crate_level_docs,
6	// TODO: missing_docs,
7	// TODO: clippy::missing_docs_in_private_items,
8    //
9
10    // Other
11	deprecated_in_future,
12	exported_private_dependencies,
13	future_incompatible,
14	missing_copy_implementations,
15	missing_debug_implementations,
16	private_in_public,
17	rust_2018_compatibility,
18	rust_2018_idioms,
19	trivial_casts,
20	trivial_numeric_casts,
21	unsafe_code,
22	unstable_features,
23	unused_import_braces,
24	unused_qualifications,
25
26	// clippy attributes
27	clippy::missing_const_for_fn,
28	clippy::redundant_pub_crate,
29	clippy::use_self
30)]
31#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_alias))]
32
33use serde::{Deserialize, Serialize};
34#[cfg(feature = "validate")]
35use serde_valid::Validate;
36use std::fmt;
37
38/// Resume Schema
39#[derive(Default, Debug, Clone, Serialize, Deserialize)]
40#[cfg_attr(feature = "validate", derive(Validate))]
41#[serde(default)]
42pub struct Resume {
43	pub basics: Option<Basics>,
44	pub work: Vec<Work>,
45	pub volunteer: Vec<Volunteer>,
46	pub education: Vec<Education>,
47	/// Specify any awards you have received throughout your professional caree
48	pub awards: Vec<Award>,
49	/// Specify any certificates you have received throughout your professional career
50	pub certificates: Vec<Certificate>,
51	/// Specify your publications through your career
52	pub publications: Vec<Publication>,
53	/// List out your professional skill-set
54	pub skills: Vec<Skill>,
55	/// List any other languages you speak
56	pub languages: Vec<Language>,
57	pub interests: Vec<Interest>,
58	/// List references you have received
59	pub references: Vec<Reference>,
60	/// Specify career projects
61	pub projects: Vec<Project>,
62	/// Specify side projects
63	#[cfg(feature = "side-projects")]
64	#[serde(rename = "sideProjects")]
65	pub side_projects: Vec<Project>,
66	/// The schema version and any other tooling configuration lives here
67	pub meta: Option<Meta>,
68}
69
70#[derive(Default, Debug, Clone, Serialize, Deserialize)]
71#[cfg_attr(feature = "validate", derive(Validate))]
72#[serde(default)]
73pub struct Basics {
74	pub name: Option<String>,
75	/// e.g. Web Developer
76	pub label: Option<String>,
77	/// URL (as per RFC 3986) to a image in JPEG or PNG format
78	pub image: Option<String>,
79	/// e.g. thomas@gmail.com
80	pub email: Option<String>,
81	/// Phone numbers are stored as strings so use any format you like, e.g. 712-117-2923
82	pub phone: Option<String>,
83	/// URL (as per RFC 3986) to your website, e.g. personal homepage
84	pub url: Option<String>,
85	/// Write a short 2-3 sentence biography about yourself
86	pub summary: Option<String>,
87	pub location: Location,
88	/// Specify any number of social networks that you participate in
89	pub profiles: Vec<Profile>,
90}
91
92#[derive(Default, Debug, Clone, Serialize, Deserialize)]
93#[cfg_attr(feature = "validate", derive(Validate))]
94#[serde(default)]
95pub struct Location {
96	/// To add multiple address lines, use \n. For example, 1234 Glücklichkeit Straße\nHinterhaus 5. Etage li.
97	pub address: Option<String>,
98	#[serde(rename = "postalCode")]
99	pub postal_code: Option<String>,
100	pub city: Option<String>,
101	/// code as per ISO-3166-1 ALPHA-2, e.g. US, AU, IN
102	#[serde(rename = "countryCode")]
103	pub country_code: Option<String>,
104	/// The general region where you live. Can be a US state, or a province, for instance.
105	pub region: Option<String>,
106}
107
108/// Specify any number of social networks that you participate in
109#[derive(Default, Debug, Clone, Serialize, Deserialize)]
110#[cfg_attr(feature = "validate", derive(Validate))]
111#[serde(default)]
112pub struct Profile {
113	/// e.g. Facebook or Twitter
114	pub network: Option<String>,
115	/// e.g. neutralthoughts
116	pub username: Option<String>,
117	/// e.g. http://twitter.example.com/neutralthoughts
118	pub url: Option<String>,
119}
120
121#[derive(Default, Debug, Clone, Serialize, Deserialize)]
122#[cfg_attr(feature = "validate", derive(Validate))]
123#[serde(default)]
124pub struct Work {
125	/// e.g. Facebook
126	pub name: Option<String>,
127	/// e.g. Menlo Park, CA
128	pub location: Option<String>,
129	/// e.g. Social Media Company
130	pub description: Option<String>,
131	/// e.g. Software Engineer
132	pub position: Option<String>,
133	/// e.g. http://facebook.example.com
134	pub url: Option<String>,
135	#[serde(rename = "startDate")]
136	#[cfg_attr(
137		feature = "validate",
138		validate(
139			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
140		)
141	)]
142	pub start_date: Option<String>,
143	#[serde(rename = "endDate")]
144	#[cfg_attr(
145		feature = "validate",
146		validate(
147			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
148		)
149	)]
150	pub end_date: Option<String>,
151	/// Specify multiple accomplishments
152	pub highlights: Vec<Highlight>,
153}
154
155/// e.g. Increased profits by 20% from 2011-2012 through viral advertising
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[cfg_attr(feature = "validate", derive(Validate))]
158pub struct Highlight(pub String);
159
160impl fmt::Display for Highlight {
161	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162		write!(f, "{}", self.0)
163	}
164}
165
166#[derive(Default, Debug, Clone, Serialize, Deserialize)]
167#[cfg_attr(feature = "validate", derive(Validate))]
168#[serde(default)]
169pub struct Volunteer {
170	/// e.g. Facebook
171	pub organization: Option<String>,
172	/// e.g. Software Engineer
173	pub position: Option<String>,
174	/// e.g. http://facebook.example.com
175	pub url: Option<String>,
176	#[serde(rename = "startDate")]
177	#[cfg_attr(
178		feature = "validate",
179		validate(
180			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
181		)
182	)]
183	pub start_date: Option<String>,
184	#[serde(rename = "endDate")]
185	#[cfg_attr(
186		feature = "validate",
187		validate(
188			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
189		)
190	)]
191	pub end_date: Option<String>,
192	/// Give an overview of your responsibilities at the company
193	pub summary: Option<String>,
194	/// Specify multiple accomplishments
195	pub highlights: Vec<Highlight>,
196}
197
198#[derive(Default, Debug, Clone, Serialize, Deserialize)]
199#[cfg_attr(feature = "validate", derive(Validate))]
200#[serde(default)]
201pub struct Education {
202	/// e.g. Massachusetts Institute of Technology
203	pub institution: Option<String>,
204	/// e.g. http://facebook.example.com
205	pub url: Option<String>,
206	/// e.g. Arts
207	pub area: Option<String>,
208	/// e.g. Bachelor
209	#[serde(rename = "studyType")]
210	pub study_type: Option<String>,
211	#[serde(rename = "startDate")]
212	#[cfg_attr(
213		feature = "validate",
214		validate(
215			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
216		)
217	)]
218	pub start_date: Option<String>,
219	#[serde(rename = "endDate")]
220	#[cfg_attr(
221		feature = "validate",
222		validate(
223			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
224		)
225	)]
226	pub end_date: Option<String>,
227	/// grade point average, e.g. 3.67/4.0
228	pub score: Option<String>,
229	/// List notable courses/subjects
230	#[serde(default)]
231	pub courses: Vec<Course>,
232}
233
234/// e.g. H1302 - Introduction to American history
235#[derive(Debug, Clone, Serialize, Deserialize)]
236#[cfg_attr(feature = "validate", derive(Validate))]
237pub struct Course(pub String);
238
239impl fmt::Display for Course {
240	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241		write!(f, "{}", self.0)
242	}
243}
244
245/// Specify any awards you have received throughout your professional caree
246#[derive(Default, Debug, Clone, Serialize, Deserialize)]
247#[cfg_attr(feature = "validate", derive(Validate))]
248#[serde(default)]
249pub struct Award {
250	/// e.g. One of the 100 greatest minds of the century
251	pub title: Option<String>,
252	#[cfg_attr(
253		feature = "validate",
254		validate(
255			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
256		)
257	)]
258	pub date: Option<String>,
259	/// e.g. Time Magazine
260	pub awarder: Option<String>,
261	/// e.g. Received for my work with Quantum Physics
262	pub summary: Option<String>,
263}
264
265/// Specify any certificates you have received throughout your professional career
266#[derive(Default, Debug, Clone, Serialize, Deserialize)]
267#[cfg_attr(feature = "validate", derive(Validate))]
268#[serde(default)]
269pub struct Certificate {
270	/// e.g. Certified Kubernetes Administrator
271	pub name: Option<String>,
272	/// e.g. 1989-06-12
273	pub date: Option<String>,
274	/// e.g. http://example.com
275	pub url: Option<String>,
276	/// e.g. CNCF
277	pub issuer: Option<String>,
278}
279
280/// Specify your publications through your career
281#[derive(Default, Debug, Clone, Serialize, Deserialize)]
282#[cfg_attr(feature = "validate", derive(Validate))]
283#[serde(default)]
284pub struct Publication {
285	/// e.g. The World Wide Web
286	pub name: Option<String>,
287	/// e.g. IEEE, Computer Magazine
288	pub publisher: Option<String>,
289	#[serde(rename = "releaseDate")]
290	#[cfg_attr(
291		feature = "validate",
292		validate(
293			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
294		)
295	)]
296	pub release_date: Option<String>,
297	/// e.g. http://www.computer.org.example.com/csdl/mags/co/1996/10/rx069-abs.html
298	pub url: Option<String>,
299	/// Short summary of publication. e.g. Discussion of the World Wide Web, HTTP, HTML.
300	pub summary: Option<String>,
301}
302
303/// List out your professional skill-set
304#[derive(Default, Debug, Clone, Serialize, Deserialize)]
305#[cfg_attr(feature = "validate", derive(Validate))]
306#[serde(default)]
307pub struct Skill {
308	/// e.g. Web Development
309	pub name: Option<String>,
310	/// e.g. Master
311	pub level: Option<String>,
312	/// List some keywords pertaining to this skill
313	pub keywords: Vec<Keyword>,
314}
315
316/// e.g. HTML
317#[derive(Debug, Clone, Serialize, Deserialize)]
318#[cfg_attr(feature = "validate", derive(Validate))]
319pub struct Keyword(pub String);
320
321impl fmt::Display for Keyword {
322	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323		write!(f, "{}", self.0)
324	}
325}
326
327/// List any other languages you speak
328#[derive(Default, Debug, Clone, Serialize, Deserialize)]
329#[cfg_attr(feature = "validate", derive(Validate))]
330#[serde(default)]
331pub struct Language {
332	/// e.g. English, Spanish
333	pub language: Option<String>,
334	/// e.g. Fluent, Beginner
335	pub fluency: Option<String>,
336}
337
338#[derive(Default, Debug, Clone, Serialize, Deserialize)]
339#[cfg_attr(feature = "validate", derive(Validate))]
340#[serde(default)]
341pub struct Interest {
342	/// e.g. Philosophy
343	pub name: Option<String>,
344	pub keywords: Vec<Keyword>,
345}
346
347/// List references you have received
348#[derive(Default, Debug, Clone, Serialize, Deserialize)]
349#[cfg_attr(feature = "validate", derive(Validate))]
350#[serde(default)]
351pub struct Reference {
352	/// e.g. Timothy Cook
353	pub name: Option<String>,
354	/// e.g. Joe blogs was a great employee, who turned up to work at least once a week. He exceeded my expectations when it came to doing nothing.
355	pub reference: Option<String>,
356}
357
358/// Specify career projects
359#[derive(Default, Debug, Clone, Serialize, Deserialize)]
360#[cfg_attr(feature = "validate", derive(Validate))]
361#[serde(default)]
362pub struct Project {
363	/// e.g. The World Wide Web
364	pub name: Option<String>,
365	/// Short summary of project. e.g. Collated works of 2017.
366	pub description: Option<String>,
367	/// Specify multiple features
368	pub highlights: Vec<Highlight>,
369	/// Specify special elements involved
370	pub keywords: Vec<Keyword>,
371	#[serde(rename = "startDate")]
372	#[cfg_attr(
373		feature = "validate",
374		validate(
375			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
376		)
377	)]
378	pub start_date: Option<String>,
379	#[serde(rename = "endDate")]
380	#[cfg_attr(
381		feature = "validate",
382		validate(
383			pattern = r"^([1-2][0-9]{3}-[0-1][0-9]-[0-3][0-9]|[1-2][0-9]{3}-[0-1][0-9]|[1-2][0-9]{3})$"
384		)
385	)]
386	pub end_date: Option<String>,
387	/// e.g. http://www.computer.org/csdl/mags/co/1996/10/rx069-abs.html
388	pub url: Option<String>,
389	/// Specify your role on this project or in company
390	pub roles: Vec<Role>,
391	/// Specify the relevant company/entity affiliations e.g. 'greenpeace', 'corporationXYZ'
392	pub entity: Option<String>,
393	/// e.g. 'volunteering', 'presentation', 'talk', 'application', 'conference'
394	pub r#type: Option<String>,
395}
396
397/// e.g. Team Lead, Speaker, Writer
398#[derive(Debug, Clone, Serialize, Deserialize)]
399#[cfg_attr(feature = "validate", derive(Validate))]
400pub struct Role(pub String);
401
402impl fmt::Display for Role {
403	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404		write!(f, "{}", self.0)
405	}
406}
407
408/// The schema version and any other tooling configuration lives here
409#[derive(Default, Debug, Clone, Serialize, Deserialize)]
410#[cfg_attr(feature = "validate", derive(Validate))]
411#[serde(default)]
412pub struct Meta {
413	/// URL (as per RFC 3986) to latest version of this document
414	pub canonical: Option<String>,
415	/// A version field which follows semver - e.g. v1.0.0
416	pub version: Option<String>,
417	/// Using ISO 8601 with YYYY-MM-DDThh:mm:ss
418	#[serde(rename = "lastModified")]
419	pub last_modified: Option<String>,
420}
421
422#[cfg(feature = "validate")]
423#[cfg(test)]
424mod validate {
425	use super::*;
426
427	#[test]
428	fn sample() -> Result<(), Box<dyn std::error::Error>> {
429		const SAMPLE: &str = include_str!("../sample.resume.json");
430
431		let resume: Resume = serde_json::from_str(SAMPLE)?;
432		resume.validate()?;
433
434		Ok(())
435	}
436
437	#[test]
438	#[ignore = "Run explicitly"]
439	fn env() -> Result<(), Box<dyn std::error::Error>> {
440		let resume_file = std::env::var_os("RESUME_FILE").unwrap();
441		let resume = std::fs::read_to_string(resume_file)?;
442
443		let resume: Resume = serde_json::from_str(&resume)?;
444		resume.validate()?;
445
446		println!("{resume:#?}");
447
448		Ok(())
449	}
450}