Skip to content
Open
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
8 changes: 5 additions & 3 deletions macros/macros_impl/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ pub fn derive_struct_impl(

if config.tag.as_ref().is_some_and(|tag| tag.is_explicit()) {
// Note: encoder must be aware if the field is optional and present, so we should not do the presence check on this level
quote!(encoder
.encode_explicit_prefix(tag, &self.0, identifier.or(Self::IDENTIFIER))
.map(drop))
quote!(
encoder
.encode_explicit_prefix(tag, &self.0, identifier.or(Self::IDENTIFIER))
.map(drop)
)
} else {
// NOTE: AsnType trait already implements correct delegate constraints, and those are passed here
// We don't need to do double intersection here!
Expand Down
33 changes: 33 additions & 0 deletions src/jer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,39 @@ mod tests {
);
}

#[test]
fn null_optional_present_vs_absent() {
// A NULL OPTIONAL field that is present (`"flag": null`) must decode as Some(()),
// not be confused with the field being absent from the JSON object.
#[derive(AsnType, Decode, Encode, Debug, PartialEq)]
#[rasn(automatic_tags)]
#[rasn(crate_root = "crate")]
struct WithNullOpt {
name: Utf8String,
flag: Option<()>,
}

// Present-null: field appears in JSON as null → Some(())
round_trip_jer!(
WithNullOpt,
WithNullOpt {
name: "test".into(),
flag: Some(())
},
r#"{"flag":null,"name":"test"}"#
);

// Absent: field omitted from JSON → None
round_trip_jer!(
WithNullOpt,
WithNullOpt {
name: "test".into(),
flag: None
},
r#"{"name":"test"}"#
);
}

#[test]
fn with_identifier_annotation() {
round_trip_jer!(
Expand Down
52 changes: 32 additions & 20 deletions src/jer/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ macro_rules! decode_jer_value {
($decoder_fn:expr, $input:expr) => {
$input
.pop()
.flatten()
.ok_or_else(|| DecodeError::from(JerDecodeErrorKind::eoi()))
.and_then($decoder_fn)
};
}

/// Decodes JSON Encoding Rules data into Rust structures.
pub struct Decoder {
stack: alloc::vec::Vec<Value>,
// `None` = field absent from the JSON object (OPTIONAL not present).
// `Some(v)` = field present with value `v` (including `Some(Value::Null)` for ASN.1 NULL).
stack: alloc::vec::Vec<Option<Value>>,
}

impl Decoder {
Expand All @@ -38,15 +41,15 @@ impl Decoder {
)
})?;
Ok(Self {
stack: alloc::vec![root],
stack: alloc::vec![Some(root)],
})
}
}

impl From<Value> for Decoder {
fn from(value: Value) -> Self {
Self {
stack: alloc::vec![value],
stack: alloc::vec![Some(value)],
}
}
}
Expand Down Expand Up @@ -82,7 +85,11 @@ impl crate::Decoder for Decoder {
})?;
(value, *size)
} else {
let last = self.stack.pop().ok_or_else(JerDecodeErrorKind::eoi)?;
let last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
Expand Down Expand Up @@ -182,7 +189,11 @@ impl crate::Decoder for Decoder {
D: Constructed<RC, EC>,
F: FnOnce(&mut Self) -> Result<D, Self::Error>,
{
let mut last = self.stack.pop().ok_or_else(JerDecodeErrorKind::eoi)?;
let mut last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object_mut()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
Expand All @@ -198,8 +209,7 @@ impl crate::Decoder for Decoder {
}
field_names.reverse();
for name in field_names {
self.stack
.push(value_map.remove(name).unwrap_or(Value::Null));
self.stack.push(value_map.remove(name));
}

(decode_fn)(self)
Expand Down Expand Up @@ -379,7 +389,11 @@ impl crate::Decoder for Decoder {
D: Fn(&mut Self::AnyDecoder<RC, EC>, usize, Tag) -> Result<FIELDS, Self::Error>,
F: FnOnce(alloc::vec::Vec<FIELDS>) -> Result<SET, Self::Error>,
{
let mut last = self.stack.pop().ok_or_else(JerDecodeErrorKind::eoi)?;
let mut last = self
.stack
.pop()
.flatten()
.ok_or_else(JerDecodeErrorKind::eoi)?;
let value_map = last
.as_object_mut()
.ok_or_else(|| JerDecodeErrorKind::TypeMismatch {
Expand All @@ -394,8 +408,7 @@ impl crate::Decoder for Decoder {
field_indices
.sort_by(|(_, a), (_, b)| a.tag_tree.smallest_tag().cmp(&b.tag_tree.smallest_tag()));
for (index, field) in field_indices.into_iter() {
self.stack
.push(value_map.remove(field.name).unwrap_or(Value::Null));
self.stack.push(value_map.remove(field.name));
fields.push((decode_fn)(self, index, field.tag)?);
}

Expand All @@ -404,8 +417,7 @@ impl crate::Decoder for Decoder {
.flat_map(|fields| fields.iter())
.enumerate()
{
self.stack
.push(value_map.remove(field.name).unwrap_or(Value::Null));
self.stack.push(value_map.remove(field.name));
fields.push((decode_fn)(self, index + SET::FIELDS.len(), field.tag)?);
}

Expand All @@ -420,11 +432,11 @@ impl crate::Decoder for Decoder {
}

fn decode_optional<D: crate::Decode>(&mut self) -> Result<Option<D>, Self::Error> {
let last = self.stack.pop().ok_or_else(JerDecodeErrorKind::eoi)?;
match last {
Value::Null => Ok(None),
v => {
self.stack.push(v);
match self.stack.pop() {
None => Err(DecodeError::from(JerDecodeErrorKind::eoi())),
Some(None) => Ok(None),
Some(Some(v)) => {
self.stack.push(Some(v));
Some(D::decode(self)).transpose()
}
}
Expand Down Expand Up @@ -609,7 +621,7 @@ impl Decoder {
.clone()
.into_iter()
.map(|v| {
self.stack.push(v);
self.stack.push(Some(v));
D::decode(self)
})
.collect()
Expand All @@ -628,7 +640,7 @@ impl Decoder {
.clone()
.into_iter()
.try_fold(SetOf::new(), |mut acc, v| {
self.stack.push(v);
self.stack.push(Some(v));
acc.insert(D::decode(self)?);
Ok(acc)
})
Expand Down Expand Up @@ -670,7 +682,7 @@ impl Decoder {
.get(i)
{
Some(t) => {
self.stack.push(v.clone());
self.stack.push(Some(v.clone()));
*t
}
None => Tag::EOC,
Expand Down
Loading