Skip to main content

nash_protocol/protocol/
canonical_string.rs

1//! Transform GraphQL JSON request into a consistent canonical string for signing
2
3use inflector::cases::snakecase::to_snake_case;
4use serde_json::{map::Map, Value};
5use std::collections::HashMap;
6
7/// Given an operation name, unstructured JSON object under the payload key (common to all
8/// mutations that require signatures) and list of fields to remove, return a canonical
9/// string representation for signing the mutation
10pub fn general_canonical_string(
11    operation_name: String,
12    mut json_value: Value,
13    without_fields: Vec<String>,
14) -> String {
15    let json_map = json_value.as_object_mut().unwrap();
16    let payload_map = json_map["payload"].as_object_mut().unwrap();
17    recursively_snake_case(payload_map, without_fields);
18    let payload_string = serde_json::to_string(&payload_map).unwrap();
19    let out = format!("{},{}", operation_name, payload_string);
20    out
21}
22
23fn recursively_snake_case(map: &mut Map<String, Value>, without: Vec<String>) {
24    let mut remove_key_map = HashMap::new();
25    for key in without {
26        remove_key_map.insert(key, true);
27    }
28    let key_list: Vec<String> = map.keys().cloned().collect();
29    for key in key_list {
30        let mut value = map.remove(&key).unwrap();
31        // replace key with snake case as long as it is not in list to remove
32        if remove_key_map.get(&to_snake_case(&key)) == None {
33            // is value is object, snake case those keys also
34            if value.is_object() {
35                let mut_next_map = value.as_object_mut().unwrap();
36                recursively_snake_case(mut_next_map, vec![]);
37            } else if value.is_string() {
38                // convert string values to lowercase
39                value = value.as_str().unwrap().to_ascii_lowercase().into();
40            } else if value.is_null() {
41                // don't serialize null fields
42                continue;
43            }
44            map.insert(to_snake_case(&key), value);
45        }
46    }
47}