Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions node/rest/src/helpers/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2019-2025 Provable Inc.
// This file is part of the snarkOS library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use snarkvm::prelude::{Network, Plaintext, Value};

use anyhow::Result;

/// Convert a `Value` to JSON.
/// `Value` implements Serialize by just calling `to_string()` on the value, which leads to Display
/// output which (sometimes) looks like JSON but isn't.
/// We produce actual JSON by calling `serde_json::to_value` on the inner object.
pub(crate) fn value_to_json<N: Network>(mapping_value: Option<&Value<N>>) -> Result<serde_json::Value> {
let json_value = if let Some(mapping_value) = mapping_value {
match mapping_value {
Value::Plaintext(plaintext) => match plaintext {
Plaintext::Array(array, _) => serde_json::to_value(array)?,
Plaintext::Struct(map, _) => serde_json::to_value(map)?,
Plaintext::Literal(literal, _) => serde_json::to_value(literal)?,
},
Value::Record(record) => serde_json::to_value(record)?,
Value::Future(future) => serde_json::to_value(future)?,
}
} else {
serde_json::Value::Object(Default::default())
};

Ok(json_value)
}

pub(crate) fn mapping_to_json<N: Network>(mapping_values: &[(Plaintext<N>, Value<N>)]) -> Result<serde_json::Value> {
let mut json = serde_json::Map::new();
for (key, value) in mapping_values {
let key = match key {
// TODO: not sure if keys can be anything other than literals
Plaintext::Array(array, _) => serde_json::to_string(&array)?,
Plaintext::Struct(map, _) => serde_json::to_string(&map)?,
// We can not use json here as it would cause the key to be escaped
Plaintext::Literal(literal, _) => literal.to_string(),
};
json.insert(key, value_to_json(Some(value))?);
}

Ok(serde_json::Value::Object(json))
}
5 changes: 5 additions & 0 deletions node/rest/src/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@
// limitations under the License.

mod auth;

/// This is used by CLI and needs to be public.
pub use auth::*;

mod error;
pub(crate) use error::*;

mod path;
pub(crate) use path::Path;

mod json;
pub(crate) use json::*;
33 changes: 19 additions & 14 deletions node/rest/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.

use super::*;
use crate::helpers::{mapping_to_json, value_to_json};

use snarkos_node_router::messages::UnconfirmedSolution;
use snarkvm::{
ledger::puzzle::Solution,
Expand Down Expand Up @@ -419,15 +421,16 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
let mapping_value = rest.ledger.vm().finalize_store().get_value_confirmed(id, name, &key)?;

// Check if metadata is requested and return the value with metadata if so.
if metadata.metadata.unwrap_or(false) {
return Ok(ErasedJson::pretty(json!({
"data": mapping_value,
let json_value = if metadata.metadata.unwrap_or(false) {
json!({
"data": value_to_json(mapping_value.as_ref())?,
"height": rest.ledger.latest_height(),
})));
}
})
} else {
value_to_json(mapping_value.as_ref())?
};

// Return the value without metadata.
Ok(ErasedJson::pretty(mapping_value))
Ok(ErasedJson::pretty(json_value))
}

/// GET /<network>/program/{programID}/mapping/{mappingName}?all={true}&metadata={true}
Expand All @@ -452,15 +455,17 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
{
Ok(Ok(mapping_values)) => {
// Check if metadata is requested and return the mapping with metadata if so.
if metadata.metadata.unwrap_or(false) {
return Ok(ErasedJson::pretty(json!({
"data": mapping_values,
let json_value = if metadata.metadata.unwrap_or(false) {
json!({
"data": mapping_to_json(&mapping_values)?,
"height": height,
})));
}
})
} else {
// Return the full mapping without metadata.
mapping_to_json(&mapping_values)?
};

// Return the full mapping without metadata.
Ok(ErasedJson::pretty(mapping_values))
Ok(ErasedJson::pretty(json_value))
}
Ok(Err(err)) => Err(RestError::internal_server_error(err.context("Unable to read mapping"))),
Err(err) => Err(RestError::internal_server_error(anyhow!("Tokio error: {err}"))),
Expand Down