use serde_json::{Map, Value};
use std::collections::HashMap;
use super::framing::{EmbedPolicy, FramingError, FramingOptions, FramingState};
use super::framing_match::matches_frame;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EmbedDecision {
Full,
Link,
Skip,
}
pub fn apply_embed_policy(
state: &mut FramingState,
subject_id: &str,
policy: EmbedPolicy,
) -> EmbedDecision {
let already_embedded = state.embedded.contains(subject_id);
match policy {
EmbedPolicy::First => {
if already_embedded {
EmbedDecision::Skip
} else {
EmbedDecision::Full
}
}
EmbedPolicy::Last => {
EmbedDecision::Full
}
EmbedPolicy::Always => {
if already_embedded {
EmbedDecision::Skip
} else {
EmbedDecision::Full
}
}
EmbedPolicy::Never => EmbedDecision::Skip,
EmbedPolicy::Link => {
if already_embedded {
EmbedDecision::Link
} else {
EmbedDecision::Full
}
}
}
}
pub fn embed_subject(
state: &mut FramingState,
subject: &Value,
frame: &Value,
subjects: &HashMap<String, Value>,
options: &FramingOptions,
) -> Result<Value, FramingError> {
let subj_obj = match subject {
Value::Object(o) => o,
_ => return Ok(subject.clone()),
};
let frame_obj = match frame {
Value::Object(o) => o,
_ => return Ok(subject.clone()),
};
let mut output: Map<String, Value> = Map::new();
if let Some(id) = subj_obj.get("@id") {
output.insert("@id".to_string(), id.clone());
}
if let Some(types) = subj_obj.get("@type") {
output.insert("@type".to_string(), types.clone());
}
for (key, val) in subj_obj.iter() {
if key.starts_with('@') && key != "@id" && key != "@type" {
output.insert(key.clone(), val.clone());
}
}
for (prop, prop_values) in subj_obj.iter() {
if prop.starts_with('@') {
continue; }
let values_arr = match prop_values {
Value::Array(arr) => arr,
_ => {
output.insert(prop.clone(), prop_values.clone());
continue;
}
};
let sub_frame: Option<&Value> = frame_obj.get(prop.as_str());
let mut embedded_values: Vec<Value> = Vec::new();
for val in values_arr {
if let Some(ref_id) = get_node_id(val) {
if let Some(ref_subject) = subjects.get(ref_id) {
let default_frame = Value::Object(Map::new());
let nested_frame = sub_frame.unwrap_or(&default_frame);
if matches_frame(ref_subject, nested_frame, options) {
let decision = apply_embed_policy(state, ref_id, options.embed.clone());
match decision {
EmbedDecision::Skip => {
embedded_values.push(serde_json::json!({"@id": ref_id}));
}
EmbedDecision::Link => {
if let Some(linked) = state.link.get(ref_id) {
embedded_values.push(linked.clone());
} else {
embedded_values.push(serde_json::json!({"@id": ref_id}));
}
}
EmbedDecision::Full => {
state.embedded.insert(ref_id.to_string());
let mut embedded = embed_subject(
state,
ref_subject,
nested_frame,
subjects,
options,
)?;
embedded = apply_explicit(&embedded, nested_frame, options);
apply_defaults(&mut embedded, nested_frame, options);
if options.embed == EmbedPolicy::Link {
state.link.insert(ref_id.to_string(), embedded.clone());
}
embedded_values.push(embedded);
}
}
} else {
embedded_values.push(serde_json::json!({"@id": ref_id}));
}
} else {
embedded_values.push(val.clone());
}
} else {
embedded_values.push(val.clone());
}
}
output.insert(prop.clone(), Value::Array(embedded_values));
}
Ok(Value::Object(output))
}
pub fn apply_explicit(subject: &Value, frame: &Value, options: &FramingOptions) -> Value {
if !options.explicit {
return subject.clone();
}
let subj_obj = match subject {
Value::Object(o) => o,
_ => return subject.clone(),
};
let frame_obj = match frame {
Value::Object(o) => o,
_ => return subject.clone(),
};
let mut pruned: Map<String, Value> = Map::new();
for (key, val) in subj_obj {
if key.starts_with('@') {
pruned.insert(key.clone(), val.clone());
continue;
}
if frame_obj.contains_key(key.as_str()) {
pruned.insert(key.clone(), val.clone());
}
}
Value::Object(pruned)
}
pub fn apply_defaults(output: &mut Value, frame: &Value, options: &FramingOptions) {
if options.omit_default {
return; }
let output_obj = match output {
Value::Object(o) => o,
_ => return,
};
let frame_obj = match frame {
Value::Object(o) => o,
_ => return,
};
for (prop, frame_val) in frame_obj {
if prop.starts_with('@') {
continue;
}
if output_obj.contains_key(prop.as_str()) {
continue;
}
if let Some(default_val) = find_default_value(frame_val) {
output_obj.insert(prop.clone(), serde_json::json!([{"@value": default_val}]));
}
}
}
fn get_node_id(value: &Value) -> Option<&str> {
match value {
Value::Object(obj) => {
if obj.contains_key("@value") {
None } else {
obj.get("@id").and_then(|v| v.as_str())
}
}
_ => None,
}
}
fn find_default_value(frame_val: &Value) -> Option<&Value> {
match frame_val {
Value::Array(arr) => {
for item in arr {
if let Value::Object(obj) = item {
if let Some(default) = obj.get("@default") {
return Some(default);
}
}
}
None
}
Value::Object(obj) => obj.get("@default"),
_ => None,
}
}