Skip to content

Commit

Permalink
refactor: creating a new intro nanopub now fails if the ORCID or name…
Browse files Browse the repository at this point in the history
… of the introduced Profile are empty
  • Loading branch information
vemonet committed Mar 30, 2024
1 parent b534f7f commit 2a7fae5
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 37 deletions.
6 changes: 3 additions & 3 deletions js/src/nanopub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Nanopub {
.map_err(|e| JsValue::from_str(&e.to_string()))
}

// TODO: optional args https://docs.rs/wasm-bindgen-derive/latest/wasm_bindgen_derive/#optional-arguments
// NOTE: optional args docs https://docs.rs/wasm-bindgen-derive/latest/wasm_bindgen_derive/#optional-arguments
#[wasm_bindgen]
pub fn publish(self, profile: &OptionNpProfile, server_url: Option<String>) -> Promise {
// Handle null/undefined profile
Expand Down Expand Up @@ -161,13 +161,13 @@ impl NpProfile {
private_key: &str,
orcid_id: &str,
name: &str,
introduction_nanopub_uri: &str,
introduction_nanopub_uri: String,
) -> Result<NpProfile, JsValue> {
RsNpProfile::new(private_key, orcid_id, name, Some(introduction_nanopub_uri))
.map(|profile: RsNpProfile| Self { profile })
.map_err(|e| JsValue::from_str(&e.to_string()))
}
// TODO: create from profile.yml file
// TODO: create from profile.yml file?

#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
Expand Down
2 changes: 1 addition & 1 deletion lib/docs/docs/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ <h1 class="text-xl font-semibold">✍️ Nanopublication signing playground 🕹
import init, { Nanopub, NpProfile, KeyPair, getNpServer } from "https://unpkg.com/@nanopub/sign";
// import init, { Nanopub, NpProfile, KeyPair, getNpServer } from "./assets/pkg/web.js";

// TODO: get ORCID based on the public key registered in the nanopub network
// TODO: get ORCID based on the public key registered in the nanopub network?
let orcidToken = null;
let orcid = "";
let privKey = null;
Expand Down
2 changes: 1 addition & 1 deletion lib/docs/docs/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ keypair = KeyPair()
new_profile = NpProfile(
private_key=keypair.private,
orcid_id="https://orcid.org/0000-0000-0000-0000",
name="",
name="Your Name",
introduction_nanopub_uri=""
)

Expand Down
2 changes: 1 addition & 1 deletion lib/docs/docs/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ use tokio::runtime;
let (private_key, _pubkey) = gen_keys().unwrap();

// Create a profile with this new private key
let profile = NpProfile::new(&private_key, "https://orcid.org/0000-0000-0000-0000", "", None).unwrap();
let profile = NpProfile::new(&private_key, "https://orcid.org/0000-0000-0000-0000", "Your Name", None).unwrap();

// Publish a nanopub introduction for this profile
let rt = runtime::Runtime::new().expect("Runtime failed");
Expand Down
44 changes: 18 additions & 26 deletions lib/src/nanopub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ use rsa::pkcs8::DecodePublicKey;
use rsa::{sha2::Digest, sha2::Sha256, Pkcs1v15Sign, RsaPublicKey};
use sophia::api::dataset::{Dataset, MutableDataset};
use sophia::api::ns::{rdf, xsd, Namespace};
use sophia::api::term::matcher::Any;
use sophia::api::term::{SimpleTerm, Term};
use sophia::api::term::{matcher::Any, Term};
use sophia::inmem::dataset::LightDataset;
use sophia::iri::{AsIriRef, Iri};
use std::collections::HashSet;
Expand All @@ -40,7 +39,7 @@ impl RdfSource for &String {
}
}

/// A nanopublication object
/// A Nanopublication, contains the nanopub info (graphs URIs, signature, etc), and the RDF dataset.
#[derive(Clone, Debug)]
pub struct Nanopub {
pub info: NpInfo,
Expand All @@ -62,7 +61,6 @@ impl fmt::Display for Nanopub {
}

impl Nanopub {
// // TODO: change approach to use Nanopub::new(rdf).sign()?
pub fn new<T: RdfSource>(rdf: T) -> Result<Self, NpError> {
let dataset = rdf.get_dataset()?;
let np_info = extract_np_info(&dataset)?;
Expand Down Expand Up @@ -122,7 +120,6 @@ impl Nanopub {
/// ```
pub fn check(self) -> Result<Self, NpError> {
let _ = self.is_valid()?;

let mut msg: String = "".to_string();
if self.info.trusty_hash.is_empty() {
msg = format!("{}1 valid (not trusty)", msg);
Expand All @@ -140,8 +137,8 @@ impl Nanopub {
msg = format!("{}1 trusty", msg);
}

// Check the signature is valid if found
let mut unsigned_dataset = self.dataset.clone();
// Check signature if found
if !self.info.signature.is_empty() {
// Remove the signature from the graph before re-generating it
unsigned_dataset.remove(
Expand Down Expand Up @@ -180,11 +177,12 @@ impl Nanopub {
BOLD, self.info.uri, END, msg
);
// let rdf = serialize_rdf(&self.dataset, &self.info.uri.as_ref(), &self.info.ns.as_ref())?;
// TODO: check if the np has been published with Nanopub::fetch
// TODO: should check return a string or a Nanopub? A string is not easy to process by machines
// Should we check if the np has been published with Nanopub::fetch?
Ok(self)
}

/// Sign an unsigned nanopub RDF and add trusty URI
/// Sign a nanopub: generate and add signature and trusty URI. If the nanopub is already signed, unsign it first.
///
/// # Arguments
///
Expand Down Expand Up @@ -249,13 +247,10 @@ impl Nanopub {
{
let now = Utc::now();
let datetime_str = now.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
// TODO: there is an error when trying to cast the string to xsd::dateTime
// let lit_date = "2019" * xsd::dateTime;
self.dataset.insert(
self.info.ns.as_iri_ref(),
ns("dct").get("created")?,
SimpleTerm::LiteralDatatype(datetime_str.into(), xsd::dateTime.iriref()),
// datetime_str.as_str() * xsd::dateTime,
datetime_str.as_str() * xsd::dateTime,
Some(&self.info.pubinfo),
)?;
}
Expand Down Expand Up @@ -334,7 +329,7 @@ impl Nanopub {
Ok(self)
}

/// Async function to sign and publish RDF to a nanopub server
/// Publish a nanopub to a nanopub server. If the nanopub is not signed and a profile is provided, it will be signed before publishing.
///
/// # Arguments
///
Expand Down Expand Up @@ -365,23 +360,19 @@ impl Nanopub {
server_url: Option<&str>,
) -> Result<Self, NpError> {
self = if let Some(profile) = profile {
// if !self.info.signature.is_empty() {
// // TODO: handle when the user provide a Profile and a signed Nanopub.
// // We should re-sign the Nanopub with the new profile
// return Err(NpError("Profile provided to sign an already signed Nanopub. Re-signing an already signed Nanopub is not supported yet".to_string()));
// }
// println!("Profile provided, signing Nanopub before publishing");
// If profile provided we sign the nanopub
self.sign(profile)?
} else if self.info.signature.is_empty() {
// If no profile and nanopub not signed we throw an error
return Err(NpError(format!(
"No profile provided and nanopub not signed, could not sign the Nanopublication \n{}",
self
)));
} else {
// If the nanopub is already signed we verify it, then publish it
// If no profile provided, but the nanopub is already signed, we verify it, then publish it
self.check()?
};
// Use test server if None provided
// Use test server if server_url not provided
let server_url = if let Some(server_url) = server_url {
if server_url.is_empty() {
TEST_SERVER.to_string()
Expand All @@ -403,7 +394,6 @@ impl Nanopub {
// BOLD, self.info.published, END
// );
} else {
println!("\n❌ Issue publishing the Nanopublication \n{}", self);
return Err(NpError(format!(
"Issue publishing the Nanopublication \n{}",
self
Expand Down Expand Up @@ -448,10 +438,6 @@ impl Nanopub {
self.info.uri = Iri::new_unchecked(NP_TEMP_URI.to_string());
self.info.ns = Namespace::new_unchecked(NP_TEMP_URI.to_string());
self.info = extract_np_info(&self.dataset)?;
// self.info.published = None;
// self.info.trusty_hash = "".to_string();
// self.info.signature = "".to_string();
// self.info.public_key = "".to_string();
Ok(self)
}

Expand Down Expand Up @@ -479,6 +465,12 @@ impl Nanopub {
/// });
/// ```
pub fn new_intro(profile: &NpProfile) -> Result<Self, NpError> {
if profile.orcid_id.is_empty() {
return Err(NpError("Invalid Profile: ORCID is empty.".to_string()));
}
if profile.name.is_empty() {
return Err(NpError("Invalid Profile: name is empty.".to_string()));
}
let mut dataset = create_base_dataset()?;
let np_ns = Namespace::new_unchecked(NP_TEMP_URI);
let assertion_graph = np_ns.get("assertion")?;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl NpProfile {
private_key: &str,
orcid_id: &str,
name: &str,
introduction_nanopub_uri: Option<&str>,
introduction_nanopub_uri: Option<String>,
) -> Result<Self, NpError> {
let privkey =
RsaPrivateKey::from_pkcs8_der(&engine::general_purpose::STANDARD.decode(private_key)?)?;
Expand All @@ -37,7 +37,7 @@ impl NpProfile {
name: name.to_string(),
public_key: get_pubkey_str(&pubkey)?,
private_key: private_key.to_string(),
introduction_nanopub_uri: Some(introduction_nanopub_uri.unwrap_or("").to_string()),
introduction_nanopub_uri,
})
}

Expand Down
8 changes: 8 additions & 0 deletions lib/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ pub fn parse_rdf(rdf: &str) -> Result<LightDataset, NpError> {
let dataset = if rdf.trim().starts_with('{') || rdf.trim().starts_with('[') {
parse_jsonld(&rdf)?
} else {
// TODO: extract prefixes https://github.com/pchampin/sophia_rs/issues/45
// The TriG parser handles nquads
trig::parse_str(&rdf)
.collect_quads()
.map_err(|e| NpError(format!("Error parsing TriG: {e}")))?
// NOTE: we can access the trig parser prefixes, but we always get an empty map, because it's not parsed yet
// let parser = trig::parse_str(&rdf);
// let prefixes = parser.0.prefixes();
// println!("PREFIXES: {:?}", prefixes);
// let dataset = parser.collect_quads()
// .map_err(|e| NpError(format!("Error parsing TriG: {e}")))?;
// dataset
};
Ok(dataset)
}
Expand Down
10 changes: 10 additions & 0 deletions lib/tests/nanopub_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ async fn publish_np_intro() -> Result<(), Box<dyn Error>> {
.await?;
// println!("{}", np);
assert!(np.info.published.is_some());
// Test error when Profile not complete
let profile = NpProfile::new(
&get_test_key(),
"https://orcid.org/0000-0000-0000-0000",
"",
None,
)?;
assert!(Nanopub::new_intro(&profile).is_err());
let profile = NpProfile::new(&get_test_key(), "", "Test User", None)?;
assert!(Nanopub::new_intro(&profile).is_err());
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion python/src/nanopub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ impl NpProfilePy {
private_key: &str,
orcid_id: &str,
name: &str,
introduction_nanopub_uri: Option<&str>,
introduction_nanopub_uri: Option<String>,
) -> PyResult<Self> {
NpProfile::new(private_key, orcid_id, name, introduction_nanopub_uri)
.map(|profile| Self { profile })
Expand Down
4 changes: 2 additions & 2 deletions python/tests/test_nanopub.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
profile = NpProfile(
private_key=private_key,
orcid_id="https://orcid.org/0000-0000-0000-0000",
name="",
name="Your Name",
introduction_nanopub_uri=""
)

Expand Down Expand Up @@ -68,7 +68,7 @@ def test_publish_intro():
new_profile = NpProfile(
private_key=keypair.private,
orcid_id="https://orcid.org/0000-0000-0000-0000",
name="",
name="Your Name",
introduction_nanopub_uri=""
)
np = Nanopub.publish_intro(new_profile)
Expand Down

0 comments on commit 2a7fae5

Please sign in to comment.