Skip to content

Add serde support to the Deserialize derive macro - #245

Draft
adampetro wants to merge 1 commit into
mainfrom
ap.serde
Draft

Add serde support to the Deserialize derive macro#245
adampetro wants to merge 1 commit into
mainfrom
ap.serde

Conversation

@adampetro

Copy link
Copy Markdown
Contributor

What this change does

The Deserialize derive macro writes code only for structs with named fields. Some types are difficult to write with this macro, for example enums and the shape of a JSON metafield.

This change adds the #[shopify_function(serde)] attribute to the derive macro. The macro then writes an implementation that dispatches to serde:

#[derive(serde::Deserialize, Deserialize, PartialEq, Debug)]
#[shopify_function(serde)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Strategy {
    Cheapest,
    MostExpensive,
    #[serde(other)]
    Unknown,
}

All serde attributes apply, because serde does the work.

Why the change exists

The primary use is the custom_scalar_overrides argument of a query. That argument accepts only a path to a type, and it refuses generic arguments. Therefore a generic wrapper type, such as SerdeAdapter<T>, cannot be a target. The attribute puts the implementation on the type itself, and the type keeps its own name:

#[typegen("./schema.graphql")]
mod schema {
    #[query("./input.graphql", custom_scalar_overrides = {
        "Query.shop.configuration" => super::Configuration,
    })]
    pub mod query {}
}

The accessor for that field then gives a &Configuration.

How it works

The new shopify_function::serde_adapter module holds a serde deserializer for the input of the Wasm API:

  • The deserializer reads the input values directly. It does not build an intermediate serde_json::Value tree.
  • The input holds all numbers as 64-bit floats. Therefore each integer method makes a range check and a check for a fraction part.
  • A string is a unit variant of an enum. An object with one property is a variant with fields.
  • deserialize_any is available. Therefore #[serde(untagged)] types and serde_json::Value also work.
  • The public from_value function keeps the error message from serde. The derive macro maps the error to read::Error::InvalidType, because that type cannot hold a message.

The derive macro refuses these combinations, and it shows a clear message for each one:

  • serde together with rename_all
  • serde together with a shopify_function attribute on a field or a variant
  • an enum without the serde attribute

Limits

All strings from the input are owned. Therefore types that borrow from the input, for example fields with #[serde(borrow)], do not work.

This change gives no support for serialization with serde.

Dependency

serde was already in the dependency tree, because serde_json, rmp-serde and example_with_targets need it. This change makes the dependency direct, and it adds no new package. The only change in Cargo.lock is one line.

Tests

  • shopify_function/tests/serde_adapter_test.rs has 11 tests: simple enums, an unknown value, a value that is not a string, error messages, nested types, a missing field, a number out of range, enums with fields, untagged enums, a generic type, and a function input.
  • shopify_function/tests/serde_custom_scalar_override_test.rs tests the full path through typegen and custom_scalar_overrides, for a required field and for an optional field.

Checks

  • cargo test for the workspace: all tests pass.
  • cargo fmt --all --check and cargo clippy --all-targets: no messages.
  • The release Wasm module of example_with_targets keeps its size of 35156 bytes.

The Deserialize derive macro writes code only for structs with named
fields. Some types are difficult to write with this macro, for example
enums and the shape of a JSON metafield.

This change adds the `#[shopify_function(serde)]` attribute. The macro
then writes an implementation that uses serde. The type keeps its own
name. Then the `custom_scalar_overrides` argument of a query can refer
to the type. A generic wrapper type cannot do this, because the argument
does not accept generic arguments.

The new `serde_adapter` module holds a serde deserializer for the input
of the Wasm API. The deserializer reads the input values directly. It
does not build an intermediate JSON tree. The public `from_value`
function keeps the error message from serde.

serde was already in the dependency tree, because serde_json needs it.
This change makes the dependency direct. The size of the Wasm output
does not change.

Assisted-By: devx/019ff170-a20d-7ca9-9f19-e3273cbb2420
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant