Skip to content

Commit 2cc0521

Browse files
committed
Add serde::Serialize support for AisMessage
1 parent 175072f commit 2cc0521

29 files changed

+201
-90
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ default = ["std"]
2020
[dependencies]
2121
nom = { version = "7", default-features = false }
2222
heapless = { version = "0.7" }
23+
serde = { version = "1.0", features = ["derive"] }
24+
serde_json = "1.0"
25+
2326

2427
[[bin]]
2528
name = "aisparser"

src/bin/aisparser.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
use ais::lib;
2+
use ais::sentence::{AisFragments, AisParser, AisSentence};
3+
use lib::std::io::{self, BufRead};
24

3-
use ais::sentence::{AisFragments, AisParser};
4-
use lib::std::io::BufRead;
5-
6-
use lib::std::io;
7-
8-
fn parse_nmea_line(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
5+
fn parse_nmea_line_to_json(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
96
let sentence = parser.parse(line, true)?;
107
if let AisFragments::Complete(sentence) = sentence {
11-
println!(
12-
"{:?}\t{:?}",
13-
lib::std::str::from_utf8(line).unwrap(),
14-
sentence.message
15-
);
8+
match serialize_to_json(&sentence) {
9+
Ok(json) => println!("{}", json),
10+
Err(err) => eprintln!("Error serializing to JSON: {}", err),
11+
}
1612
}
1713
Ok(())
1814
}
1915

16+
pub fn serialize_to_json(sentence: &AisSentence) -> serde_json::Result<String> {
17+
serde_json::to_string(sentence)
18+
}
19+
20+
pub fn deserialize_from_json(json_data: &str) -> serde_json::Result<AisSentence> {
21+
serde_json::from_str(json_data)
22+
}
23+
2024
fn main() {
2125
let mut parser = AisParser::new();
2226
let stdin = io::stdin();
23-
{
24-
let handle = stdin.lock();
27+
let handle = stdin.lock();
2528

26-
handle
27-
.split(b'\n')
28-
.map(|line| line.unwrap())
29-
.for_each(|line| {
30-
parse_nmea_line(&mut parser, &line).unwrap_or_else(|err| {
31-
eprintln!("{:?}\t{:?}", lib::std::str::from_utf8(&line).unwrap(), err);
32-
});
29+
handle
30+
.split(b'\n')
31+
.map(|line| line.unwrap())
32+
.for_each(|line| {
33+
parse_nmea_line_to_json(&mut parser, &line).unwrap_or_else(|err| {
34+
eprintln!(
35+
"Error parsing line: {:?}\t{:?}",
36+
lib::std::str::from_utf8(&line).unwrap(),
37+
err
38+
);
3339
});
34-
}
40+
});
3541
}

src/lib.rs

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,44 @@
1-
//! AIS parsing library, for reading AIS NMEA sentences
1+
//! AIS parsing library for reading AIS NMEA sentences, with support for JSON serialization.
22
//!
3-
//! Given an NMEA stream, this library can extract various AIS message types in more detail.
3+
//! This library parses NMEA AIS (Automatic Identification System) sentences and provides
4+
//! structured representations of the data, allowing further processing or analysis.
45
//!
5-
//! # Example:
6+
//! # Features
7+
//! - Parses AIS NMEA sentences into structured types.
8+
//! - Supports JSON serialization and deserialization for `AisSentence` objects.
9+
//!
10+
//! # Example
611
//! ```
7-
//! use ais::{AisFragments, AisParser};
12+
//! use ais::{AisFragments, AisParser, serialize_to_json, deserialize_from_json};
813
//! use ais::messages::AisMessage;
914
//!
10-
//! // The line below is an NMEA sentence, much as you'd see coming out of an AIS decoder.
1115
//! let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";
12-
//!
1316
//! let mut parser = AisParser::new();
14-
//! if let AisFragments::Complete(sentence) = parser.parse(line, true)? {
15-
//! // This sentence is complete, ie unfragmented
17+
//!
18+
//! if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
1619
//! assert_eq!(sentence.num_fragments, 1);
17-
//! // The data was transmitted on AIS channel B
1820
//! assert_eq!(sentence.channel, Some('B'));
1921
//!
20-
//! if let Some(message) = sentence.message {
22+
//! if let Some(ref message) = sentence.message {
2123
//! match message {
2224
//! AisMessage::AidToNavigationReport(report) => {
2325
//! assert_eq!(report.mmsi, 993692028);
2426
//! assert_eq!(report.name, "SF OAK BAY BR VAIS E");
25-
//! // There are a ton more fields available here
2627
//! },
2728
//! _ => panic!("Unexpected message type"),
2829
//! }
2930
//! }
31+
//!
32+
//! let json = serialize_to_json(&sentence).unwrap();
33+
//! let deserialized_sentence = deserialize_from_json(&json).unwrap();
34+
//! assert_eq!(sentence, deserialized_sentence);
3035
//! }
31-
//! # Ok::<(), ais::errors::Error>(())
3236
//! ```
37+
3338
#![cfg_attr(not(feature = "std"), no_std)]
3439

3540
#[doc(hidden)]
36-
/// standard library stuff available crate-wide, regardless of `no_std` state
41+
/// Standard library items, available crate-wide regardless of `no_std` state.
3742
pub mod lib {
3843
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
3944
pub mod std {
@@ -80,6 +85,19 @@ pub mod sentence;
8085
pub use errors::Result;
8186
pub use sentence::{AisFragments, AisParser};
8287

88+
use sentence::AisSentence;
89+
use serde_json::Error as SerdeError;
90+
91+
/// Serializes an `AisSentence` to JSON
92+
pub fn serialize_to_json(sentence: &AisSentence) -> std::result::Result<String, SerdeError> {
93+
serde_json::to_string(sentence)
94+
}
95+
96+
/// Deserializes an `AisSentence` from JSON
97+
pub fn deserialize_from_json(json_data: &str) -> std::result::Result<AisSentence, SerdeError> {
98+
serde_json::from_str(json_data)
99+
}
100+
83101
#[cfg(test)]
84102
mod test_helpers {
85103
#[inline]
@@ -94,6 +112,7 @@ mod test_helpers {
94112
#[cfg(test)]
95113
mod tests {
96114
use super::*;
115+
use crate::sentence::{AisReportType, AisSentence, TalkerId};
97116

98117
const TEST_MESSAGES: [&[u8]; 8] = [
99118
b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01",
@@ -108,9 +127,54 @@ mod tests {
108127

109128
#[test]
110129
fn end_to_end() {
111-
let mut parser = sentence::AisParser::new();
130+
let mut parser = AisParser::new();
112131
for line in TEST_MESSAGES.iter() {
113132
parser.parse(line, true).unwrap();
114133
}
115134
}
135+
136+
#[test]
137+
fn test_json_serialization() {
138+
let mut parser = AisParser::new();
139+
let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";
140+
141+
if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
142+
// Serialize the sentence to JSON
143+
let json = serialize_to_json(&sentence).expect("Failed to serialize to JSON");
144+
println!("Serialized JSON: {}", json);
145+
146+
// Deserialize back from JSON
147+
let deserialized_sentence =
148+
deserialize_from_json(&json).expect("Failed to deserialize from JSON");
149+
150+
assert_eq!(sentence, deserialized_sentence);
151+
}
152+
}
153+
154+
#[test]
155+
fn test_serialize_deserialize() {
156+
// Create a sample AisSentence struct
157+
let original_sentence = AisSentence {
158+
message: None,
159+
talker_id: TalkerId::AI,
160+
report_type: AisReportType::VDM,
161+
num_fragments: 1,
162+
fragment_number: 1,
163+
message_id: Some(123),
164+
channel: Some('A'),
165+
data: vec![69, 62, 107, 98, 57, 79], // sample data; replace with real data if needed
166+
fill_bit_count: 0,
167+
message_type: 1,
168+
};
169+
170+
// Serialize to JSON
171+
let json_data = serialize_to_json(&original_sentence).expect("Serialization failed");
172+
173+
// Deserialize back to an AisSentence
174+
let deserialized_sentence: AisSentence =
175+
deserialize_from_json(&json_data).expect("Deserialization failed");
176+
177+
// Check if the deserialized struct matches the original
178+
assert_eq!(original_sentence, deserialized_sentence);
179+
}
116180
}

src/messages/addressed_safety_related.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use crate::errors::Result;
55
use nom::bits::{bits, complete::take as take_bits};
66
use nom::combinator::map;
77
use nom::IResult;
8+
use serde::{Deserialize, Serialize};
89

9-
#[derive(Debug, PartialEq)]
10+
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
1011
pub struct AddressedSafetyRelatedMessage {
1112
pub message_type: u8,
1213
pub repeat_indicator: u8,

src/messages/aid_to_navigation_report.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use crate::errors::Result;
77
use nom::bits::{bits, complete::take as take_bits};
88
use nom::combinator::map;
99
use nom::IResult;
10+
use serde::{Deserialize, Serialize};
1011

11-
#[derive(Debug, PartialEq, Eq)]
12+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1213
pub enum NavaidType {
1314
ReferencePoint,
1415
Racon,
@@ -84,7 +85,7 @@ impl NavaidType {
8485
}
8586
}
8687

87-
#[derive(Debug, PartialEq)]
88+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
8889
pub struct AidToNavigationReport {
8990
pub message_type: u8,
9091
pub repeat_indicator: u8,

src/messages/assignment_mode_command.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use super::AisMessageType;
55
use crate::errors::Result;
66
use nom::bits::{bits, complete::take as take_bits};
77
use nom::IResult;
8+
use serde::{Deserialize, Serialize};
89

9-
#[derive(Debug, PartialEq, Eq)]
10+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1011
pub struct AssignmentModeCommand {
1112
pub message_type: u8,
1213
pub repeat_indicator: u8,

src/messages/base_station_report.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use crate::errors::Result;
88
use nom::bits::{bits, complete::take as take_bits};
99
use nom::combinator::map;
1010
use nom::IResult;
11+
use serde::{Deserialize, Serialize};
1112

12-
#[derive(Debug, PartialEq)]
13+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
1314
pub struct BaseStationReport {
1415
pub message_type: u8,
1516
pub repeat_indicator: u8,

src/messages/binary_acknowledge.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
//! Binary Acknowledge (type 7)
2-
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
3-
use super::nom_noalloc::many_m_n;
2+
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
3+
use super::nom_noalloc::many_m_n;
44
use super::AisMessageType;
55
use crate::errors::Result;
66
use crate::lib;
77
use nom::bits::{bits, complete::take as take_bits};
8-
#[cfg(any(feature = "std", feature = "alloc"))]
8+
#[cfg(any(feature = "std", feature = "alloc"))]
99
use nom::multi::many_m_n;
1010
use nom::IResult;
11+
use serde::{Deserialize, Serialize};
1112

12-
#[derive(Debug, PartialEq, Eq)]
13+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1314
pub struct Acknowledgement {
1415
pub mmsi: u32,
1516
pub seq_num: u8,
@@ -28,7 +29,7 @@ pub type AcknowledgementList = lib::std::vec::Vec<Acknowledgement>;
2829
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
2930
pub type AcknowledgementList = lib::std::vec::Vec<Acknowledgement, 4>;
3031

31-
#[derive(Debug, PartialEq, Eq)]
32+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
3233
pub struct BinaryAcknowledge {
3334
pub message_type: u8,
3435
pub repeat_indicator: u8,

src/messages/binary_addressed.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::lib;
66
use nom::bits::{bits, complete::take as take_bits};
77
use nom::combinator::map;
88
use nom::IResult;
9+
use serde::{Deserialize, Serialize};
910

1011
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
1112
const MAX_DATA_SIZE_BYTES: usize = 119;
@@ -15,7 +16,7 @@ pub type MessageData = lib::std::vec::Vec<u8>;
1516
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
1617
pub type MessageData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;
1718

18-
#[derive(Debug, PartialEq)]
19+
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
1920
pub struct BinaryAddressedMessage {
2021
pub message_type: u8,
2122
pub repeat_indicator: u8,

src/messages/binary_broadcast_message.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::errors::Result;
44
use crate::lib;
55
use nom::bits::{bits, complete::take as take_bits};
66
use nom::IResult;
7+
use serde::{Deserialize, Serialize};
78

89
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
910
const MAX_DATA_SIZE_BYTES: usize = 119;
@@ -13,7 +14,7 @@ pub type MessageData = lib::std::vec::Vec<u8>;
1314
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
1415
pub type MessageData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;
1516

16-
#[derive(Debug, PartialEq, Eq)]
17+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1718
pub struct BinaryBroadcastMessage {
1819
pub message_type: u8,
1920
pub repeat_indicator: u8,

0 commit comments

Comments
 (0)