-
Notifications
You must be signed in to change notification settings - Fork 14
feat!: Use binary envelopes for operation lower_func encoding #2447
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
172f85e
Better lower func serialization errors
aborgna-q 7f61088
better error reporting on for serde_with envelopes
aborgna-q 7acf296
Update schema comment
aborgna-q 4e9e1ee
Fix doclink visibility
aborgna-q cda40c0
encode binary hugrs...
aborgna-q 152507a
cross-compat tests
aborgna-q File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,9 @@ use crate::std_extensions::STD_REG; | |||||
| /// De/Serialize a package or hugr by encoding it into a textual Envelope and | ||||||
| /// storing it as a string. | ||||||
| /// | ||||||
| /// This is similar to [`AsBinaryEnvelope`], but uses a textual envelope instead | ||||||
| /// of a binary one. | ||||||
| /// | ||||||
| /// Note that only PRELUDE extensions are used to decode the package's content. | ||||||
| /// When serializing a HUGR, any additional extensions required to load it are | ||||||
| /// embedded in the envelope. Packages should manually add any required | ||||||
|
|
@@ -45,9 +48,53 @@ use crate::std_extensions::STD_REG; | |||||
| /// When reading an encoded HUGR, the `AsStringEnvelope` deserializer will first | ||||||
| /// try to decode the value as an string-encoded envelope. If that fails, it | ||||||
| /// will fallback to decoding the legacy HUGR serde definition. This temporary | ||||||
| /// compatibility layer is meant to be removed in 0.21.0. | ||||||
| /// compatibility is required to support `hugr <= 0.19` and will be removed in | ||||||
| /// a future version. | ||||||
| pub struct AsStringEnvelope; | ||||||
|
|
||||||
| /// De/Serialize a package or hugr by encoding it into a binary envelope and | ||||||
| /// storing it as a base64-encoded string. | ||||||
| /// | ||||||
| /// This is similar to [`AsStringEnvelope`], but uses a binary envelope instead | ||||||
| /// of a string. | ||||||
| /// When deserializing, if the string starts with the envelope magic 'HUGRiHJv' | ||||||
| /// it will be loaded as a string envelope without base64 decoding. | ||||||
| /// | ||||||
| /// Note that only PRELUDE extensions are used to decode the package's content. | ||||||
| /// When serializing a HUGR, any additional extensions required to load it are | ||||||
| /// embedded in the envelope. Packages should manually add any required | ||||||
| /// extensions before serializing. | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ```rust | ||||||
| /// # use serde::{Deserialize, Serialize}; | ||||||
| /// # use serde_json::json; | ||||||
| /// # use serde_with::{serde_as}; | ||||||
| /// # use hugr_core::Hugr; | ||||||
| /// # use hugr_core::package::Package; | ||||||
| /// # use hugr_core::envelope::serde_with::AsBinaryEnvelope; | ||||||
| /// # | ||||||
| /// #[serde_as] | ||||||
| /// #[derive(Deserialize, Serialize)] | ||||||
| /// struct A { | ||||||
| /// #[serde_as(as = "AsBinaryEnvelope")] | ||||||
| /// package: Package, | ||||||
| /// #[serde_as(as = "Vec<AsBinaryEnvelope>")] | ||||||
| /// hugrs: Vec<Hugr>, | ||||||
| /// } | ||||||
| /// ``` | ||||||
| /// | ||||||
| /// # Backwards compatibility | ||||||
| /// | ||||||
| /// When reading an encoded HUGR, the `AsBinaryEnvelope` deserializer will first | ||||||
| /// try to decode the value as an binary-encoded envelope. If that fails, it | ||||||
| /// will fallback to decoding a string envelope instead, and then finally to | ||||||
| /// decoding the legacy HUGR serde definition. This temporary compatibility | ||||||
| /// layer is required to support `hugr <= 0.19` and will be removed in a future | ||||||
| /// version. | ||||||
| pub struct AsBinaryEnvelope; | ||||||
|
|
||||||
| /// Implements [`serde_with::DeserializeAs`] and [`serde_with::SerializeAs`] for | ||||||
| /// the helper to deserialize `Hugr` and `Package` types, using the given | ||||||
| /// extension registry. | ||||||
|
|
@@ -211,3 +258,213 @@ macro_rules! impl_serde_as_string_envelope { | |||||
| pub use impl_serde_as_string_envelope; | ||||||
|
|
||||||
| impl_serde_as_string_envelope!(AsStringEnvelope, &STD_REG); | ||||||
|
|
||||||
| /// Implements [`serde_with::DeserializeAs`] and [`serde_with::SerializeAs`] for | ||||||
| /// the helper to deserialize `Hugr` and `Package` types, using the given | ||||||
| /// extension registry. | ||||||
| /// | ||||||
| /// This macro is used to implement the default [`AsBinaryEnvelope`] wrapper. | ||||||
| /// | ||||||
| /// # Parameters | ||||||
| /// | ||||||
| /// - `$adaptor`: The name of the adaptor type to implement. | ||||||
| /// - `$extension_reg`: A reference to the extension registry to use for deserialization. | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ```rust | ||||||
| /// # use serde::{Deserialize, Serialize}; | ||||||
| /// # use serde_json::json; | ||||||
| /// # use serde_with::{serde_as}; | ||||||
| /// # use hugr_core::Hugr; | ||||||
| /// # use hugr_core::package::Package; | ||||||
| /// # use hugr_core::envelope::serde_with::AsBinaryEnvelope; | ||||||
| /// # use hugr_core::envelope::serde_with::impl_serde_as_binary_envelope; | ||||||
| /// # use hugr_core::extension::ExtensionRegistry; | ||||||
| /// # | ||||||
| /// struct CustomAsEnvelope; | ||||||
| /// | ||||||
| /// impl_serde_as_binary_envelope!(CustomAsEnvelope, &hugr_core::extension::EMPTY_REG); | ||||||
| /// | ||||||
| /// #[serde_as] | ||||||
| /// #[derive(Deserialize, Serialize)] | ||||||
| /// struct A { | ||||||
| /// #[serde_as(as = "CustomAsEnvelope")] | ||||||
| /// package: Package, | ||||||
| /// } | ||||||
| /// ``` | ||||||
| /// | ||||||
| #[macro_export] | ||||||
| macro_rules! impl_serde_as_binary_envelope { | ||||||
| ($adaptor:ident, $extension_reg:expr) => { | ||||||
| impl<'de> serde_with::DeserializeAs<'de, $crate::package::Package> for $adaptor { | ||||||
| fn deserialize_as<D>(deserializer: D) -> Result<$crate::package::Package, D::Error> | ||||||
| where | ||||||
| D: serde::Deserializer<'de>, | ||||||
| { | ||||||
| struct Helper; | ||||||
| impl serde::de::Visitor<'_> for Helper { | ||||||
| type Value = $crate::package::Package; | ||||||
|
|
||||||
| fn expecting( | ||||||
| &self, | ||||||
| formatter: &mut std::fmt::Formatter<'_>, | ||||||
| ) -> std::fmt::Result { | ||||||
| formatter.write_str("a base64-encoded envelope") | ||||||
| } | ||||||
|
|
||||||
| fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> | ||||||
| where | ||||||
| E: serde::de::Error, | ||||||
| { | ||||||
| use $crate::envelope::serde_with::base64::{DecoderReader, STANDARD}; | ||||||
|
|
||||||
| let extensions: &$crate::extension::ExtensionRegistry = $extension_reg; | ||||||
|
|
||||||
| if value | ||||||
| .as_bytes() | ||||||
| .starts_with($crate::envelope::MAGIC_NUMBERS) | ||||||
| { | ||||||
| // If the string starts with the envelope magic 'HUGRiHJv', | ||||||
| // skip the base64 decoding. | ||||||
| let reader = std::io::Cursor::new(value.as_bytes()); | ||||||
| $crate::package::Package::load(reader, Some(extensions)) | ||||||
| .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) | ||||||
| } else { | ||||||
| let reader = DecoderReader::new(value.as_bytes(), &STANDARD); | ||||||
| let buf_reader = std::io::BufReader::new(reader); | ||||||
| $crate::package::Package::load(buf_reader, Some(extensions)) | ||||||
| .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| deserializer.deserialize_str(Helper) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl<'de> serde_with::DeserializeAs<'de, $crate::Hugr> for $adaptor { | ||||||
| fn deserialize_as<D>(deserializer: D) -> Result<$crate::Hugr, D::Error> | ||||||
| where | ||||||
| D: serde::Deserializer<'de>, | ||||||
| { | ||||||
| struct Helper; | ||||||
| impl<'vis> serde::de::Visitor<'vis> for Helper { | ||||||
| type Value = $crate::Hugr; | ||||||
|
|
||||||
| fn expecting( | ||||||
| &self, | ||||||
| formatter: &mut std::fmt::Formatter<'_>, | ||||||
| ) -> std::fmt::Result { | ||||||
| formatter.write_str("a base64-encoded envelope") | ||||||
| } | ||||||
|
|
||||||
| fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> | ||||||
| where | ||||||
| E: serde::de::Error, | ||||||
| { | ||||||
| use $crate::envelope::serde_with::base64::{DecoderReader, STANDARD}; | ||||||
|
|
||||||
| let extensions: &$crate::extension::ExtensionRegistry = $extension_reg; | ||||||
|
|
||||||
| if value | ||||||
| .as_bytes() | ||||||
| .starts_with($crate::envelope::MAGIC_NUMBERS) | ||||||
| { | ||||||
| // If the string starts with the envelope magic 'HUGRiHJv', | ||||||
| // skip the base64 decoding. | ||||||
| let reader = std::io::Cursor::new(value.as_bytes()); | ||||||
| $crate::Hugr::load(reader, Some(extensions)) | ||||||
| .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) | ||||||
| } else { | ||||||
| let reader = DecoderReader::new(value.as_bytes(), &STANDARD); | ||||||
| let buf_reader = std::io::BufReader::new(reader); | ||||||
| $crate::Hugr::load(buf_reader, Some(extensions)) | ||||||
| .map_err(|e| serde::de::Error::custom(format!("{e:?}"))) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> | ||||||
| where | ||||||
| A: serde::de::MapAccess<'vis>, | ||||||
| { | ||||||
| // Backwards compatibility: If the encoded value is not a | ||||||
| // string, we may have a legacy HUGR serde structure instead. In that | ||||||
| // case, we can add an envelope header and try again. | ||||||
| // | ||||||
| // TODO: Remove this fallback in 0.21.0 | ||||||
| let deserializer = serde::de::value::MapAccessDeserializer::new(map); | ||||||
| #[allow(deprecated)] | ||||||
| let mut hugr = | ||||||
| $crate::hugr::serialize::serde_deserialize_hugr(deserializer) | ||||||
| .map_err(serde::de::Error::custom)?; | ||||||
|
|
||||||
| let extensions: &$crate::extension::ExtensionRegistry = $extension_reg; | ||||||
| hugr.resolve_extension_defs(extensions) | ||||||
| .map_err(serde::de::Error::custom)?; | ||||||
| Ok(hugr) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // TODO: Go back to `deserialize_str` once the fallback is removed. | ||||||
| deserializer.deserialize_any(Helper) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl serde_with::SerializeAs<$crate::package::Package> for $adaptor { | ||||||
| fn serialize_as<S>( | ||||||
| source: &$crate::package::Package, | ||||||
| serializer: S, | ||||||
| ) -> Result<S::Ok, S::Error> | ||||||
| where | ||||||
| S: serde::Serializer, | ||||||
| { | ||||||
| use $crate::envelope::serde_with::base64::{EncoderStringWriter, STANDARD}; | ||||||
|
|
||||||
| let mut writer = EncoderStringWriter::new(&STANDARD); | ||||||
| source | ||||||
| .store(&mut writer, $crate::envelope::EnvelopeConfig::binary()) | ||||||
| .map_err(serde::ser::Error::custom)?; | ||||||
| let str = writer.into_inner(); | ||||||
| serializer.collect_str(&str) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl serde_with::SerializeAs<$crate::Hugr> for $adaptor { | ||||||
| fn serialize_as<S>(source: &$crate::Hugr, serializer: S) -> Result<S::Ok, S::Error> | ||||||
| where | ||||||
| S: serde::Serializer, | ||||||
| { | ||||||
| // Include any additional extension required to load the HUGR in the envelope. | ||||||
| let extensions: &$crate::extension::ExtensionRegistry = $extension_reg; | ||||||
| let mut extra_extensions = $crate::extension::ExtensionRegistry::default(); | ||||||
| for ext in $crate::hugr::views::HugrView::extensions(source).iter() { | ||||||
| if !extensions.contains(ext.name()) { | ||||||
| extra_extensions.register_updated(ext.clone()); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| let str = source | ||||||
| .store_str_with_exts( | ||||||
| $crate::envelope::EnvelopeConfig::text(), | ||||||
|
||||||
| $crate::envelope::EnvelopeConfig::text(), | |
| $crate::envelope::EnvelopeConfig::binary(), |
? maybe I've misunderstood
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.