Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serde::Serialize support for AisMessage #25

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ default = ["std"]
[dependencies]
nom = { version = "7", default-features = false }
heapless = { version = "0.7" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"


[[bin]]
name = "aisparser"
Expand Down
48 changes: 27 additions & 21 deletions src/bin/aisparser.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
use ais::lib;
use ais::sentence::{AisFragments, AisParser, AisSentence};
use lib::std::io::{self, BufRead};

use ais::sentence::{AisFragments, AisParser};
use lib::std::io::BufRead;

use lib::std::io;

fn parse_nmea_line(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
fn parse_nmea_line_to_json(parser: &mut AisParser, line: &[u8]) -> Result<(), ais::errors::Error> {
let sentence = parser.parse(line, true)?;
if let AisFragments::Complete(sentence) = sentence {
println!(
"{:?}\t{:?}",
lib::std::str::from_utf8(line).unwrap(),
sentence.message
);
match serialize_to_json(&sentence) {
Ok(json) => println!("{}", json),
Err(err) => eprintln!("Error serializing to JSON: {}", err),
}
}
Ok(())
}

pub fn serialize_to_json(sentence: &AisSentence) -> serde_json::Result<String> {
serde_json::to_string(sentence)
}

pub fn deserialize_from_json(json_data: &str) -> serde_json::Result<AisSentence> {
serde_json::from_str(json_data)
}

fn main() {
let mut parser = AisParser::new();
let stdin = io::stdin();
{
let handle = stdin.lock();
let handle = stdin.lock();

handle
.split(b'\n')
.map(|line| line.unwrap())
.for_each(|line| {
parse_nmea_line(&mut parser, &line).unwrap_or_else(|err| {
eprintln!("{:?}\t{:?}", lib::std::str::from_utf8(&line).unwrap(), err);
});
handle
.split(b'\n')
.map(|line| line.unwrap())
.for_each(|line| {
parse_nmea_line_to_json(&mut parser, &line).unwrap_or_else(|err| {
eprintln!(
"Error parsing line: {:?}\t{:?}",
lib::std::str::from_utf8(&line).unwrap(),
err
);
});
}
});
}
93 changes: 79 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
//! AIS parsing library, for reading AIS NMEA sentences
//! AIS parsing library for reading AIS NMEA sentences, with support for JSON serialization.
//!
//! Given an NMEA stream, this library can extract various AIS message types in more detail.
//! This library parses NMEA AIS (Automatic Identification System) sentences and provides
//! structured representations of the data, allowing further processing or analysis.
//!
//! # Example:
//! # Features
//! - Parses AIS NMEA sentences into structured types.
//! - Supports JSON serialization and deserialization for `AisSentence` objects.
//!
//! # Example
//! ```
//! use ais::{AisFragments, AisParser};
//! use ais::{AisFragments, AisParser, serialize_to_json, deserialize_from_json};
//! use ais::messages::AisMessage;
//!
//! // The line below is an NMEA sentence, much as you'd see coming out of an AIS decoder.
//! let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";
//!
//! let mut parser = AisParser::new();
//! if let AisFragments::Complete(sentence) = parser.parse(line, true)? {
//! // This sentence is complete, ie unfragmented
//!
//! if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
//! assert_eq!(sentence.num_fragments, 1);
//! // The data was transmitted on AIS channel B
//! assert_eq!(sentence.channel, Some('B'));
//!
//! if let Some(message) = sentence.message {
//! if let Some(ref message) = sentence.message {
//! match message {
//! AisMessage::AidToNavigationReport(report) => {
//! assert_eq!(report.mmsi, 993692028);
//! assert_eq!(report.name, "SF OAK BAY BR VAIS E");
//! // There are a ton more fields available here
//! },
//! _ => panic!("Unexpected message type"),
//! }
//! }
//!
//! let json = serialize_to_json(&sentence).unwrap();
//! let deserialized_sentence = deserialize_from_json(&json).unwrap();
//! assert_eq!(sentence, deserialized_sentence);
//! }
//! # Ok::<(), ais::errors::Error>(())
//! ```

#![cfg_attr(not(feature = "std"), no_std)]

#[doc(hidden)]
/// standard library stuff available crate-wide, regardless of `no_std` state
/// Standard library items, available crate-wide regardless of `no_std` state.
pub mod lib {
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub mod std {
Expand Down Expand Up @@ -80,6 +85,20 @@
pub use errors::Result;
pub use sentence::{AisFragments, AisParser};

use sentence::AisSentence;
use serde_json::Error as SerdeError;

/// Serializes an `AisSentence` to JSON
#[cfg(any(feature = "std", feature = "alloc"))]
use lib::std::string::String;
pub fn serialize_to_json(sentence: &AisSentence) -> std::result::Result<String, SerdeError> {

Check failure

Code scanning / clippy

failed to resolve: use of undeclared crate or module std Error

failed to resolve: use of undeclared crate or module std

Check failure

Code scanning / clippy

cannot find type String in this scope Error

cannot find type String in this scope
serde_json::to_string(sentence)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn deserialize_from_json(json_data: &str) -> std::result::Result<AisSentence, SerdeError> {

Check failure

Code scanning / clippy

failed to resolve: use of undeclared crate or module std Error

failed to resolve: use of undeclared crate or module std
serde_json::from_str(json_data)
}

#[cfg(test)]
mod test_helpers {
#[inline]
Expand All @@ -94,6 +113,7 @@
#[cfg(test)]
mod tests {
use super::*;
use crate::sentence::{AisReportType, AisSentence, TalkerId};

const TEST_MESSAGES: [&[u8]; 8] = [
b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01",
Expand All @@ -108,9 +128,54 @@

#[test]
fn end_to_end() {
let mut parser = sentence::AisParser::new();
let mut parser = AisParser::new();
for line in TEST_MESSAGES.iter() {
parser.parse(line, true).unwrap();
}
}

#[test]
fn test_json_serialization() {
let mut parser = AisParser::new();
let line = b"!AIVDM,1,1,,B,E>kb9O9aS@7PUh10dh19@;0Tah2cWrfP:l?M`00003vP100,0*01";

if let AisFragments::Complete(sentence) = parser.parse(line, true).unwrap() {
// Serialize the sentence to JSON
let json = serialize_to_json(&sentence).expect("Failed to serialize to JSON");
println!("Serialized JSON: {}", json);

// Deserialize back from JSON
let deserialized_sentence =
deserialize_from_json(&json).expect("Failed to deserialize from JSON");

assert_eq!(sentence, deserialized_sentence);
}
}

#[test]
fn test_serialize_deserialize() {
// Create a sample AisSentence struct
let original_sentence = AisSentence {
message: None,
talker_id: TalkerId::AI,
report_type: AisReportType::VDM,
num_fragments: 1,
fragment_number: 1,
message_id: Some(123),
channel: Some('A'),
data: vec![69, 62, 107, 98, 57, 79], // sample data; replace with real data if needed
fill_bit_count: 0,
message_type: 1,
};

// Serialize to JSON
let json_data = serialize_to_json(&original_sentence).expect("Serialization failed");

// Deserialize back to an AisSentence
let deserialized_sentence: AisSentence =
deserialize_from_json(&json_data).expect("Deserialization failed");

// Check if the deserialized struct matches the original
assert_eq!(original_sentence, deserialized_sentence);
}
}
3 changes: 2 additions & 1 deletion src/messages/addressed_safety_related.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
pub struct AddressedSafetyRelatedMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/aid_to_navigation_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum NavaidType {
ReferencePoint,
Racon,
Expand Down Expand Up @@ -84,7 +85,7 @@
}
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::String<20>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::String<20>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::String<20>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::String<20>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct AidToNavigationReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/assignment_mode_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use super::AisMessageType;
use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AssignmentModeCommand {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/base_station_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::errors::Result;
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BaseStationReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
11 changes: 6 additions & 5 deletions src/messages/binary_acknowledge.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! Binary Acknowledge (type 7)
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
use super::nom_noalloc::many_m_n;
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
use super::nom_noalloc::many_m_n;
use super::AisMessageType;
use crate::errors::Result;
use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
#[cfg(any(feature = "std", feature = "alloc"))]
#[cfg(any(feature = "std", feature = "alloc"))]
use nom::multi::many_m_n;
use nom::IResult;
use serde::{Deserialize, Serialize};

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

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::Vec<messages::binary_acknowledge::Acknowledgement, 4>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::Vec<messages::binary_acknowledge::Acknowledgement, 4>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::Vec<messages::binary_acknowledge::Acknowledgement, 4>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::Vec<messages::binary_acknowledge::Acknowledgement, 4>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct BinaryAcknowledge {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/binary_addressed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

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

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct BinaryAddressedMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/binary_broadcast_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::lib;
use nom::bits::{bits, complete::take as take_bits};
use nom::IResult;
use serde::{Deserialize, Serialize};

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

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct BinaryBroadcastMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/data_link_management_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
#[cfg(any(feature = "std", feature = "alloc"))]
use nom::multi::many_m_n;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SlotReservation {
pub offset: u16,
pub num_slots: u8,
Expand Down Expand Up @@ -40,7 +41,7 @@
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type SlotReservationList = lib::std::vec::Vec<SlotReservation, 4>;

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::Vec<messages::data_link_management_message::SlotReservation, 4>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::Vec<messages::data_link_management_message::SlotReservation, 4>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::Vec<messages::data_link_management_message::SlotReservation, 4>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::Vec<messages::data_link_management_message::SlotReservation, 4>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct DataLinkManagementMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
5 changes: 3 additions & 2 deletions src/messages/dgnss_broadcast_binary_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
const MAX_DATA_SIZE_BYTES: usize = 119;
Expand All @@ -15,7 +16,7 @@
#[cfg(all(not(feature = "std"), not(feature = "alloc")))]
pub type CorrectionData = lib::std::vec::Vec<u8, MAX_DATA_SIZE_BYTES>;

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct DgnssBroadcastBinaryMessage {
pub message_type: u8,
pub repeat_indicator: u8,
Expand All @@ -25,7 +26,7 @@
pub payload: DifferentialCorrectionData,
}

#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::Vec<u8, 119>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct DifferentialCorrectionData {
pub message_type: u8,
pub station_id: u16,
Expand Down
3 changes: 2 additions & 1 deletion src/messages/extended_class_b_position_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
use nom::bits::{bits, complete::take as take_bits};
use nom::combinator::map;
use nom::IResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]

Check failure

Code scanning / clippy

the trait bound heapless::String<20>: messages::_::_serde::Serialize is not satisfied Error

the trait bound heapless::String<20>: messages::_::_serde::Serialize is not satisfied

Check failure

Code scanning / clippy

the trait bound heapless::String<20>: messages::_::_serde::Deserialize<'_> is not satisfied Error

the trait bound heapless::String<20>: messages::_::_serde::Deserialize<'_> is not satisfied
pub struct ExtendedClassBPositionReport {
pub message_type: u8,
pub repeat_indicator: u8,
Expand Down
Loading
Loading