HDK: basic functions

### HDK & HDI - Holochain Development Kit (HDK) - To create a coordinator zome, create a rust crate that depends on the HDK and compile it - Allows to define zome functions that can be called from outside the zome - Docs at https://docs.rs/hdk - Holochain Deterministic Integrity (HDI) - Subset of the HDK to build integrity zomes - Only types that deal with validation rules and entry definitions - Docs at https://docs.rs/hdi
### Defining Zome Functions: Exposure

use hdk::prelude::*;

#[hdk_extern] // - Comes from hdk::prelude::*
              // - Exposes a function to be callable from the outside
fn zome_function_hello(name: String) -> ExternResult<String> { // - Zome function name 
                                                               // must be unique in this zome
                                                               // - Zome functions can only 
                                                               // accept one parameter

  Ok(format!("hello {}!", name)) // Must return an "ExternResult" of a typing that implements 
                                 // "Serialize", "Deserialize" and "Debug"
}

#[hdk_extern]
fn zome_function_hello_world(_: ()) -> ExternResult<String> { // To indicate disregard 
                                                              // for input parameter, we use
                                                              // the unit typing "()"
  Ok(format!("hello world!"))
}
### Defining Zome Functions: Custom Inputs

use hdk::prelude::*;

// We can define custom input parameter structs
#[derive(Serialize, Deserialize, Debug)] // Input parameters must derive 
                                         // "Serialize", "Deserialize" and "Debug"
struct CustomInputStruct {
  name: String
}

#[hdk_extern]
fn zome_function_with_custom_input(input: CustomInputStruct) -> ExternResult<String> { // Ok
  Ok(format!("hello {}!", input.name))
}

struct BrokenInputStruct { // This input struct won't work,
                           // because it doesn't derive the right traits
  name: String
}

#[hdk_extern]
fn zome_function_with_broken_custom_input(
  input: BrokenInputStruct // Error: Input parameters must derive 
                           // "Serialize", "Deserialize" and "Debug"
) -> ExternResult<String> { 
  Ok(format!("hello {}!", input.name))
}
### Defining Zome Functions: Capturing Debugging Data - You'll need to use the `wasm_error!()` macro to return errors. - You'll need to use `.map_err(|err| wasm_error!(err))` for some HDK function calls. - You can use `error!()` or `warn!()` in your zome functions to log to the terminal console.

use hdk::prelude::*;

#[hdk_extern]
fn returning_errors(_: ()) -> ExternResult<()> { 
  Err(wasm_error!( // "wasm_error!" Captures debugging information (eg. line numbers)
    WasmErrorInner::Guest(String::from("This function returns an error"))
  )) 
}

#[hdk_extern]
fn debugging(_: ()) -> ExternResult<()> { 
  error!("This is an error log that functions just like println");
  warn!("This is a warn log that functions just like println");

  Ok(())
}
### Retrieving Static Cell Information (I)

use hdk::prelude::*;

#[hdk_extern]
fn get_my_pub_key(_: ()) -> ExternResult<AgentPubKey> { // "AgentPubKey" acts as the "agent ID"
  let my_agent_info = agent_info()?; // Will return a different value depending on 
                                     // which cell is executing the function

  let my_pub_key: AgentPubKey = my_agent_info.agent_initial_pubkey; // In the future the  
                                                                    // public key of a cell 
                                                                    // may change

  Ok(my_pub_key)
}
            
#[hdk_extern]
fn get_zome_name(_: ()) -> ExternResult<ZomeName> { // "ZomeName" is a simple Tuple(Struct)
                                                    // wrapping a String
  let my_zome_info = zome_info()?; // Will return a different value depending on 
                                   // which zome this function is defined within,
                                   // but will return the same for all agents

  let zome_name: ZomeName = my_zome_info.name; // Human readable name, 
                                               // defined in the DNA manifest
  let zome_id: ZomeId = my_zome_info.id; // Zome index (integer) in the DNA that Holochain 
                                         // uses to identify the zome 

  Ok(zome_name)
}

### Retrieving Static Cell Information (II)

use hdk::prelude::*;

#[hdk_extern]
fn get_dna_hash(_: ()) -> ExternResult<DnaHash> {
  let my_dna_info = dna_info()?; // Will return the same value for all agents 
                                 // and zomes in the same DNA

  let dna_name: String = my_dna_info.name; // Human readable name, defined in the DNA manifest
  let dna_hash: DnaHash = my_dna_info.hash; // Hash of source code, network seed and properties
  let dna_properties = my_dna_info.properties; // Properties: configuration that the 
                                               // DNA can read to modify it's behaviour
  let pubkey = AgentPubKey::try_from(dna_properties)?; // 'dna_properties' is of type 
                                                       // 'SerializedBytes', so it can 
                                                       // serialize to any rust typing 
                                                       // that derives 'Serialize' and 
                                                       // 'Deserialize'

  Ok(dna_hash)
}
### Defining An Entry Type - Defines the format for a particular type of entry - Can only be done in an integrity zome

// zomes/integrity/lib.rs
use hdi::prelude::*;

#[hdk_entry_helper] // Adds necessary trait implementations
struct Comment {
  comment: String
}

#[hdk_entry_helper]
struct Post {
  title: String,
  content: String
}

#[hdk_entry_defs] // - Defines all the entries for this zome
                  // - Must be the only "#[hdk_entry_defs]" in the zome
                  // - Can only be defined in integrity zomes
#[unit_enum(UnitEntryTypes)] // Defines a "UnitEntryTypes" enum that's a copy of "EntryTypes" 
                             // but with the converted variants to unit variants (no payload)
enum EntryTypes { // Enum with each entry type as a variant
  #[entry_def( // Configures entry behaviour
    name = "comment", // Must be unique across entry types
    visibility = "private" // Entry not published to the DHT, only stored in the source chain
  )] 
  Comment(Comment),
  // Can be omitted, defaults to the struct name in snake_case and "public"
  Post(Post),
}
### Entry Actions #### Overview - These are actions you can take on your Holochain app's state machine, stored in the app's DHT - Includes actions like create, update, delete - Can be done in an integrity zome or in a coordinator zome
### Entry Actions #### create_entry() - Issues a notice to the DHT to record the creation of a new entry node for some given type - Records both the details of the entry itself, and a create action, which describes a claim to having "created" that entry - In the merkle tree, a create action branches directly from an entry itself - If the submitted entry already can already be found in the DHT, then only the create action is added (since there can be no duplicates in a content-addressed DHT) - There can be any number of create actions added to the DHT, all claiming to have created the same entry - The create actions may contradict one another (for example, by claiming different authors of the entry) - It's up to the application code to interpret the full set of discoverable actions, and determine a single, well-believed world state
### Entry Actions #### create_entry()

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome

#[hdk_extern]
fn create_comment(comment: Comment) -> ExternResult<()> {
  create_entry(EntryTypes::Comment(comment))?; // - "create_entry" comes from hdk::prelude::*; 
                                               // - Input must be an instance of the 
                                               // entry types enumerable

  Ok(())
}

#[hdk_extern]
fn create_comment_and_return_action_hash(comment: Comment) -> ExternResult<ActionHash> {
  let action_hash = create_entry(EntryTypes::Comment(comment))?; // create_entry() returns 
                                                                 // the hash of the action 
                                                                 // that has just been inserted
                                                                 // into the agent's 
                                                                 // source chain
  Ok(action_hash)
}
### Entry Actions #### update_entry() - Records a modification submitted on an entry, downstream of that entry's initial create action - An update action must be issued as a child of some other action - Can be the child of either a create action, or of another pre-existing update action - After adding an update, the original entry will still exist in the DHT - And so will all of that entry's upstream create and update actions - One create or update action may have any number of child update actions added under it - In this way, a set of update actions can form a merkle chain of compatible updates to one entry - Furthermore, a set of update actions might branch to form a tree of incompatible update chains - It's up to the application code to interpret the full set of discoverable actions, and determine a single well-believed world state - An update action cannot branch directly from an entry itself - This is because, for any given entry, there may be several valid create and update actions all descended from that one entry - Attempting to attach an update directly to an entry would require deciding which of the valid action chains you wanted to append the new update action to
### Entry Actions #### update_entry()

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome

#[derive(Serialize, Deserialize, Debug)]
struct UpdateCommentInput {
  original_action_hash: ActionHash,
  new_comment: Comment
}

#[hdk_extern]
fn update_comment(input: UpdateCommentInput) -> ExternResult<ActionHash> {
  let action_hash = update_entry(
    input.original_action_hash, // The original action must be a create or an update action
    EntryTypes::Comment(input.new_comment) // New entry must be of the same entrytype
  )?;

  Ok(action_hash)
}
### Entry Actions #### delete_entry() - Must be scoped to an action, not an entry - Deleted entry and action will still exist in the DHT after deleting - We are just marking them as "deleted" - One action may be deleted multiple times - It's up to the application to interpret what "deleting an action" means

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome

#[hdk_extern]
fn delete_comment(comment_action_hash: ActionHash) -> ExternResult<()> {
  let action_hash = delete_entry(comment_action_hash)?; // - Deleting an entry is also its 
                                                        // own action
                                                        // - Deleted action hash must be 
                                                        // a create or an update action

  Ok(())
}
### Entry Retrievals #### Overview - Read requests issued to the DHT
### Entry Retrievals #### get() - Given an entry hash or an action hash, return the "Record" that hashes to that hash - Record is the union of the action and optionally its accompanying entry
              
// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome
            
#[hdk_extern]
fn get_comment(comment_action_hash: ActionHash) -> ExternResult<Option<Record>> {
  let comment_record: Option<Record> = get(
    comment_action_hash, // Accepts an "ActionHash" or an "EntryHash"
    GetOptions::default() // Cache control, by default returns cached records
  )?;

  /* In case you need to extract the entry struct from the record */

  let maybe_entry: Option<Entry> = record
    .entry
    .into_option();

  let entry: Entry =  maybe_entry.ok_or(wasm_error!(
    WasmErrorInner::Guest(String::from("This record doesn't include any entry"))
  ))?;

  let _comment = Comment::try_from(entry)?;

  Ok(comment_record)
}

            
### Entry Retrievals #### get_details() (I) - Given an action hash, return the action, its associated entry, and all the actions that refer to that action

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome

#[hdk_extern]
fn get_comment_action_details(comment_action_hash: ActionHash) -> ExternResult<Record> {
  let record_details: Option<Details> = get_details(
    comment_action_hash, // Accepts an "ActionHash" or an "EntryHash"
    GetOptions::default() // Same options as "get()"
  )?;

  match record_details {
    Some(Details::Record(RecordDetails { record, deletes, updates, .. }))  => Ok(record),
    _ => Err(wasm_error!(
      WasmErrorInner::Guest(String::from("Error trying to get the details of this action"))
    ))
  }
}
### Entry Retrievals #### get_details() (II) - Given an entry hash, return the entry and all the actions that refer to that entry

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{EntryTypes, Comment}; // Import the types defined in our integrity zome

#[hdk_extern]
fn get_comment_entry_details(comment_entry_hash: EntryHash) -> ExternResult<Entry> {
  let entry_details: Option<Details> = get_details(
    comment_entry_hash, // Entry Hash
    GetOptions::default()
  )?;
  
  match entry_details {
    Some(Details::Entry(EntryDetails { entry, actions, deletes, updates, .. })) => Ok(entry),
    _ => Err(wasm_error!(
      WasmErrorInner::Guest(String::from("Error trying to get the details of this action"))
    ))
  }
}
### Links - Linking from one hash to another, with some optional metadata - Link components: - Base hash - can be: - public key - entry hash - action hash - external hash (doesn't exist in this DHT) - Target hash: can be of the same types as the base hash - Type: app defined link type - Tag: app defined arbitrary metadata - Neither the base nor the target hash need to exist as actions or entries in the DHT - Defining link types - Only in integrity zomes

// zomes/integrity/lib.rs
use hdi::prelude::*;

// Defining link types
#[hdk_link_types] // - Defines all the link types for this zome
                  // - Must be the only "#[hdk_link_types]" in the zome
                  // - Can only be defined in integrity zomes
enum LinkTypes { // Enum with each entry type as a variant
   AuthorToComment, // Must be a Unit variant (without any payload)
}
### Link Actions #### create_link()

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::{LinkTypes, EntryTypes, Comment}; // Import the types defined 
                                                      // in our integrity zome

#[hdk_extern]
fn create_comment(comment: Comment) -> ExternResult<ActionHash> {
  let comment_action_hash = create_entry(EntryTypes::Comment(comment))?; // Create comment 

  let my_pub_key: AgentPubKey = agent_info()?.agent_initial_pubkey; // The author of the 
                                                                    // comment is the agent 
                                                                    // executing this 
                                                                    // function call

  let create_link_action_hash: ActionHash = create_link( // Creating a link is 
                                                         // an action in itself
    my_pub_key, // Base hash: my agent pub key
    comment_action_hash, // Target hash: the action hash that has just been created
    LinkTypes::AuthorToComment, // LinkType: one of the variants defined in the integrity zome
    () // Link tag, can be any struct that derives 'Serialized' and 'Deserialized'
  )?;

  Ok(comment_action_hash)
}
### Link Retrievals #### get_links() - Links are attached to the base hash - You can retrieve all the links attached to a given hash as a base

// zomes/coordinator/lib.rs
use hdk::prelude::*;
use integrity_zome::*; // Import the types defined in our integrity zome

#[hdk_extern]
fn get_all_comments_by_agent(author: AgentPubKey) -> ExternResult<Vec<Record>> {
  let links: Vec<Link> = get_links(
    author, // Base hash: the given agent pub key
    LinkTypes::AuthorToComment, // LinkType: one of the variants defined in the integrity zome
    None, // "Option<LinkTag>": filter result set down to only those links 
          // with a matching prefix on their link tag
  )?;
  let mut comments: Vec<Record> = vec![];
  for link in links {
    if let Some(record) = get(
      ActionHash::from(link.target), // - Conversion is important to specify the 
                                     // type of hash we are retrieving for
                                     // - Will be the action hash for each of the 
                                     // comments that the agent has created
      GetOptions::default()
    )? {
      comments.push(record);
    }
  }
  Ok(comments)
}

That's it!