diff --git a/Cargo.lock b/Cargo.lock index a83caed..9f9c61e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,30 +1,73 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "ascii" -version = "0.9.3" +name = "ariadne" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" +checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89" +dependencies = [ + "unicode-width", + "yansi", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] -name = "combine" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +name = "bluejay-core" +version = "0.2.0" +source = "git+https://github.com/Shopify/bluejay?rev=fc9a7417ab0275151f5e3fa38d78839d5222618e#fc9a7417ab0275151f5e3fa38d78839d5222618e" dependencies = [ - "ascii", - "byteorder", - "either", - "memchr", - "unreachable", + "enum-as-inner", + "itertools", + "serde_json", + "strum", +] + +[[package]] +name = "bluejay-parser" +version = "0.2.0" +source = "git+https://github.com/Shopify/bluejay?rev=fc9a7417ab0275151f5e3fa38d78839d5222618e#fc9a7417ab0275151f5e3fa38d78839d5222618e" +dependencies = [ + "ariadne", + "bluejay-core", + "enum-as-inner", + "itertools", + "logos", + "strum", +] + +[[package]] +name = "bluejay-typegen-codegen" +version = "0.2.0" +source = "git+https://github.com/Shopify/bluejay?rev=fc9a7417ab0275151f5e3fa38d78839d5222618e#fc9a7417ab0275151f5e3fa38d78839d5222618e" +dependencies = [ + "bluejay-core", + "bluejay-parser", + "bluejay-validator", + "convert_case", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bluejay-validator" +version = "0.2.0" +source = "git+https://github.com/Shopify/bluejay?rev=fc9a7417ab0275151f5e3fa38d78839d5222618e#fc9a7417ab0275151f5e3fa38d78839d5222618e" +dependencies = [ + "bluejay-core", + "bluejay-parser", + "itertools", + "paste", + "seq-macro", + "serde_json", ] [[package]] @@ -43,108 +86,117 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] -name = "example" -version = "1.0.0" +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "graphql_client", - "graphql_client_codegen", - "serde", - "serde_json", - "shopify_function", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "example_with_targets" -version = "1.0.0" -dependencies = [ - "graphql_client", - "graphql_client_codegen", - "serde", - "serde_json", - "shopify_function", -] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "graphql-introspection-query" -version = "0.2.0" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" -dependencies = [ - "serde", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "graphql-parser" -version = "0.4.0" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "combine", - "thiserror", + "either", ] [[package]] -name = "graphql_client" -version = "0.14.0" +name = "itoa" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50cfdc7f34b7f01909d55c2dcb71d4c13cbcbb4a1605d6c8bd760d654c1144b" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "logos" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db" dependencies = [ - "graphql_query_derive", - "serde", - "serde_json", + "logos-derive", ] [[package]] -name = "graphql_client_codegen" -version = "0.14.0" +name = "logos-codegen" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e27ed0c2cf0c0cc52c6bcf3b45c907f433015e580879d14005386251842fb0a" +checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e" dependencies = [ - "graphql-introspection-query", - "graphql-parser", - "heck", + "beef", + "fnv", "lazy_static", "proc-macro2", "quote", - "serde", - "serde_json", - "syn 1.0.109", + "regex-syntax", + "rustc_version", + "syn", ] [[package]] -name = "graphql_query_derive" -version = "0.14.0" +name = "logos-derive" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83febfa838f898cfa73dfaa7a8eb69ff3409021ac06ee94cfb3d622f6eeb1a97" +checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba" dependencies = [ - "graphql_client_codegen", - "proc-macro2", - "syn 1.0.109", + "logos-codegen", ] [[package]] -name = "heck" -version = "0.4.1" +name = "memchr" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] -name = "itoa" -version = "1.0.10" +name = "mini-internal" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "07b7f1340a7f0d2f89755aac4096f1c55352bfcb95cb8f351d6fcefa18df474f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "miniserde" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "625208b7bc90255fe6d99338041880bf48a3049166f46fbefbd4e9b0478c0b66" +dependencies = [ + "itoa", + "mini-internal", + "ryu", +] [[package]] -name = "memchr" -version = "2.7.1" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "proc-macro2" @@ -164,12 +216,45 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "seq-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + [[package]] name = "serde" version = "1.0.215" @@ -187,7 +272,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn", ] [[package]] @@ -206,8 +291,7 @@ dependencies = [ name = "shopify_function" version = "0.8.1" dependencies = [ - "graphql_client", - "graphql_client_codegen", + "miniserde", "ryu", "serde", "serde_json", @@ -218,53 +302,43 @@ dependencies = [ name = "shopify_function_macro" version = "0.8.1" dependencies = [ - "convert_case", - "graphql_client_codegen", + "bluejay-typegen-codegen", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] -name = "syn" -version = "1.0.109" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "strum_macros", ] [[package]] -name = "syn" -version = "2.0.85" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ + "heck", "proc-macro2", "quote", - "unicode-ident", + "rustversion", + "syn", ] [[package]] -name = "thiserror" -version = "1.0.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.57" +name = "syn" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "unicode-ident", ] [[package]] @@ -280,16 +354,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] -name = "unreachable" -version = "1.0.0" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "void" -version = "1.0.2" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index 770546e..9fb6208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,6 @@ [workspace] resolver = "2" members = [ - "example", - "example_with_targets", "shopify_function", "shopify_function_macro", ] diff --git a/README.md b/README.md index af1a435..e959506 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,4 @@ For documentation, please take a look at the [crate's docs.rs page][docs]. To se [crate]: https://crates.io/crates/shopify-function [docs]: https://docs.rs/shopify_function -[example]: https://github.com/Shopify/shopify-function-rust/tree/main/example \ No newline at end of file +[example_with_targets]: https://github.com/Shopify/shopify-function-rust/tree/main/example_with_targets diff --git a/example/.output.graphql b/example/.output.graphql deleted file mode 100644 index cd95859..0000000 --- a/example/.output.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation Output($result: FunctionResult!) { - handleResult(result: $result) -} diff --git a/example/Cargo.toml b/example/Cargo.toml deleted file mode 100644 index f11df3b..0000000 --- a/example/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "example" -version = "1.0.0" -edition = "2021" -license = "MIT" - -[dependencies] -shopify_function = { path = "../shopify_function" } -serde = { version = "1.0.215", features = ["derive"] } -serde_json = "1.0" -graphql_client = "0.14.0" -graphql_client_codegen = "0.14.0" diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 0314b3e..0000000 --- a/example/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Shopify Rust function example - -This is an example of how to use the [Shopify Function Rust crate][crate] to write a Shopify Function. - -[crate]: https://crates.io/crates/shopify-function diff --git a/example/input.graphql b/example/input.graphql deleted file mode 100644 index dbe5644..0000000 --- a/example/input.graphql +++ /dev/null @@ -1,23 +0,0 @@ -query Input { - cart { - lines { - quantity - cost { - totalAmount { - amount - } - } - merchandise { - __typename - ...on ProductVariant { - id - } - } - } - } - discountNode { - metafield(namespace: "some-space", key: "function-config") { - value - } - } -} diff --git a/example/schema.graphql b/example/schema.graphql deleted file mode 100644 index b996aa2..0000000 --- a/example/schema.graphql +++ /dev/null @@ -1,3578 +0,0 @@ -schema { - query: Input - mutation: MutationRoot -} - -""" -Exactly one field of input must be provided, and all others omitted. -""" -directive @oneOf on INPUT_OBJECT - -""" -Represents a generic custom attribute. -""" -type Attribute { - """ - Key or name of the attribute. - """ - key: String! - - """ - Value of the attribute. - """ - value: String -} - -""" -Represents information about the buyer that is interacting with the cart. -""" -type BuyerIdentity { - """ - The customer associated with the cart. - """ - customer: Customer - - """ - The email address of the buyer that is interacting with the cart. - """ - email: String - - """ - The phone number of the buyer that is interacting with the cart. - """ - phone: String -} - -""" -A cart represents the merchandise that a buyer intends to purchase, and the cost associated with the cart. -""" -type Cart { - """ - The attributes associated with the cart. Attributes are represented as key-value pairs. - """ - attribute( - """ - The key of the attribute to retrieve. - """ - key: String - ): Attribute - - """ - Information about the buyer that is interacting with the cart. - """ - buyerIdentity: BuyerIdentity - - """ - The costs that the buyer will pay at checkout. - """ - cost: CartCost! - - """ - The delivery groups available for the cart based on the buyer's shipping address. - """ - deliveryGroups: [CartDeliveryGroup!]! - - """ - A list of lines containing information about the items the customer intends to purchase. - """ - lines: [CartLine!]! -} - -""" -The cost that the buyer will pay at checkout. -""" -type CartCost { - """ - The amount, before taxes and discounts, for the customer to pay. - """ - subtotalAmount: MoneyV2! - - """ - The total amount for the customer to pay. - """ - totalAmount: MoneyV2! - - """ - The duty amount for the customer to pay at checkout. - """ - totalDutyAmount: MoneyV2 - - """ - The tax amount for the customer to pay at checkout. - """ - totalTaxAmount: MoneyV2 -} - -""" -Information about the options available for one or more line items to be delivered to a specific address. -""" -type CartDeliveryGroup { - """ - A list of cart lines for the delivery group. - """ - cartLines: [CartLine!]! - - """ - The destination address for the delivery group. - """ - deliveryAddress: MailingAddress - - """ - The delivery options available for the delivery group. - """ - deliveryOptions: [CartDeliveryOption!]! - - """ - Unique identifier for the delivery group. - """ - id: ID! - - """ - Information about the delivery option the buyer has selected. - """ - selectedDeliveryOption: CartDeliveryOption -} - -""" -Information about a delivery option. -""" -type CartDeliveryOption { - """ - The code of the delivery option. - """ - code: String - - """ - The cost for the delivery option. - """ - cost: MoneyV2! - - """ - The method for the delivery option. - """ - deliveryMethodType: DeliveryMethod! - - """ - The description of the delivery option. - """ - description: String - - """ - The title of the delivery option. - """ - title: String -} - -""" -Represents information about the merchandise in the cart. -""" -type CartLine { - """ - Retrieve a cart line attribute by key. - - Cart line attributes are also known as line item properties in Liquid. - """ - attribute( - """ - The key of the attribute to retrieve. - """ - key: String - ): Attribute - - """ - The cost of the merchandise line that the buyer will pay at checkout. - """ - cost: CartLineCost! - - """ - The ID of the cart line. - """ - id: ID! - - """ - The merchandise that the buyer intends to purchase. - """ - merchandise: Merchandise! - - """ - The quantity of the merchandise that the customer intends to purchase. - """ - quantity: Int! -} - -""" -The cost of the merchandise line that the buyer will pay at checkout. -""" -type CartLineCost { - """ - The amount of the merchandise line. - """ - amountPerQuantity: MoneyV2! - - """ - The compare at amount of the merchandise line. - """ - compareAtAmountPerQuantity: MoneyV2 - - """ - The cost of the merchandise line before line-level discounts. - """ - subtotalAmount: MoneyV2! - - """ - The total cost of the merchandise line. - """ - totalAmount: MoneyV2! -} - -""" -A country. -""" -type Country { - """ - The ISO code of the country. - """ - isoCode: CountryCode! -} - -""" -The code designating a country/region, which generally follows ISO 3166-1 alpha-2 guidelines. -If a territory doesn't have a country code value in the `CountryCode` enum, then it might be considered a subdivision -of another country. For example, the territories associated with Spain are represented by the country code `ES`, -and the territories associated with the United States of America are represented by the country code `US`. -""" -enum CountryCode { - """ - Ascension Island. - """ - AC - - """ - Andorra. - """ - AD - - """ - United Arab Emirates. - """ - AE - - """ - Afghanistan. - """ - AF - - """ - Antigua & Barbuda. - """ - AG - - """ - Anguilla. - """ - AI - - """ - Albania. - """ - AL - - """ - Armenia. - """ - AM - - """ - Netherlands Antilles. - """ - AN - - """ - Angola. - """ - AO - - """ - Argentina. - """ - AR - - """ - Austria. - """ - AT - - """ - Australia. - """ - AU - - """ - Aruba. - """ - AW - - """ - Åland Islands. - """ - AX - - """ - Azerbaijan. - """ - AZ - - """ - Bosnia & Herzegovina. - """ - BA - - """ - Barbados. - """ - BB - - """ - Bangladesh. - """ - BD - - """ - Belgium. - """ - BE - - """ - Burkina Faso. - """ - BF - - """ - Bulgaria. - """ - BG - - """ - Bahrain. - """ - BH - - """ - Burundi. - """ - BI - - """ - Benin. - """ - BJ - - """ - St. Barthélemy. - """ - BL - - """ - Bermuda. - """ - BM - - """ - Brunei. - """ - BN - - """ - Bolivia. - """ - BO - - """ - Caribbean Netherlands. - """ - BQ - - """ - Brazil. - """ - BR - - """ - Bahamas. - """ - BS - - """ - Bhutan. - """ - BT - - """ - Bouvet Island. - """ - BV - - """ - Botswana. - """ - BW - - """ - Belarus. - """ - BY - - """ - Belize. - """ - BZ - - """ - Canada. - """ - CA - - """ - Cocos (Keeling) Islands. - """ - CC - - """ - Congo - Kinshasa. - """ - CD - - """ - Central African Republic. - """ - CF - - """ - Congo - Brazzaville. - """ - CG - - """ - Switzerland. - """ - CH - - """ - Côte d’Ivoire. - """ - CI - - """ - Cook Islands. - """ - CK - - """ - Chile. - """ - CL - - """ - Cameroon. - """ - CM - - """ - China. - """ - CN - - """ - Colombia. - """ - CO - - """ - Costa Rica. - """ - CR - - """ - Cuba. - """ - CU - - """ - Cape Verde. - """ - CV - - """ - Curaçao. - """ - CW - - """ - Christmas Island. - """ - CX - - """ - Cyprus. - """ - CY - - """ - Czechia. - """ - CZ - - """ - Germany. - """ - DE - - """ - Djibouti. - """ - DJ - - """ - Denmark. - """ - DK - - """ - Dominica. - """ - DM - - """ - Dominican Republic. - """ - DO - - """ - Algeria. - """ - DZ - - """ - Ecuador. - """ - EC - - """ - Estonia. - """ - EE - - """ - Egypt. - """ - EG - - """ - Western Sahara. - """ - EH - - """ - Eritrea. - """ - ER - - """ - Spain. - """ - ES - - """ - Ethiopia. - """ - ET - - """ - Finland. - """ - FI - - """ - Fiji. - """ - FJ - - """ - Falkland Islands. - """ - FK - - """ - Faroe Islands. - """ - FO - - """ - France. - """ - FR - - """ - Gabon. - """ - GA - - """ - United Kingdom. - """ - GB - - """ - Grenada. - """ - GD - - """ - Georgia. - """ - GE - - """ - French Guiana. - """ - GF - - """ - Guernsey. - """ - GG - - """ - Ghana. - """ - GH - - """ - Gibraltar. - """ - GI - - """ - Greenland. - """ - GL - - """ - Gambia. - """ - GM - - """ - Guinea. - """ - GN - - """ - Guadeloupe. - """ - GP - - """ - Equatorial Guinea. - """ - GQ - - """ - Greece. - """ - GR - - """ - South Georgia & South Sandwich Islands. - """ - GS - - """ - Guatemala. - """ - GT - - """ - Guinea-Bissau. - """ - GW - - """ - Guyana. - """ - GY - - """ - Hong Kong SAR. - """ - HK - - """ - Heard & McDonald Islands. - """ - HM - - """ - Honduras. - """ - HN - - """ - Croatia. - """ - HR - - """ - Haiti. - """ - HT - - """ - Hungary. - """ - HU - - """ - Indonesia. - """ - ID - - """ - Ireland. - """ - IE - - """ - Israel. - """ - IL - - """ - Isle of Man. - """ - IM - - """ - India. - """ - IN - - """ - British Indian Ocean Territory. - """ - IO - - """ - Iraq. - """ - IQ - - """ - Iran. - """ - IR - - """ - Iceland. - """ - IS - - """ - Italy. - """ - IT - - """ - Jersey. - """ - JE - - """ - Jamaica. - """ - JM - - """ - Jordan. - """ - JO - - """ - Japan. - """ - JP - - """ - Kenya. - """ - KE - - """ - Kyrgyzstan. - """ - KG - - """ - Cambodia. - """ - KH - - """ - Kiribati. - """ - KI - - """ - Comoros. - """ - KM - - """ - St. Kitts & Nevis. - """ - KN - - """ - North Korea. - """ - KP - - """ - South Korea. - """ - KR - - """ - Kuwait. - """ - KW - - """ - Cayman Islands. - """ - KY - - """ - Kazakhstan. - """ - KZ - - """ - Laos. - """ - LA - - """ - Lebanon. - """ - LB - - """ - St. Lucia. - """ - LC - - """ - Liechtenstein. - """ - LI - - """ - Sri Lanka. - """ - LK - - """ - Liberia. - """ - LR - - """ - Lesotho. - """ - LS - - """ - Lithuania. - """ - LT - - """ - Luxembourg. - """ - LU - - """ - Latvia. - """ - LV - - """ - Libya. - """ - LY - - """ - Morocco. - """ - MA - - """ - Monaco. - """ - MC - - """ - Moldova. - """ - MD - - """ - Montenegro. - """ - ME - - """ - St. Martin. - """ - MF - - """ - Madagascar. - """ - MG - - """ - North Macedonia. - """ - MK - - """ - Mali. - """ - ML - - """ - Myanmar (Burma). - """ - MM - - """ - Mongolia. - """ - MN - - """ - Macao SAR. - """ - MO - - """ - Martinique. - """ - MQ - - """ - Mauritania. - """ - MR - - """ - Montserrat. - """ - MS - - """ - Malta. - """ - MT - - """ - Mauritius. - """ - MU - - """ - Maldives. - """ - MV - - """ - Malawi. - """ - MW - - """ - Mexico. - """ - MX - - """ - Malaysia. - """ - MY - - """ - Mozambique. - """ - MZ - - """ - Namibia. - """ - NA - - """ - New Caledonia. - """ - NC - - """ - Niger. - """ - NE - - """ - Norfolk Island. - """ - NF - - """ - Nigeria. - """ - NG - - """ - Nicaragua. - """ - NI - - """ - Netherlands. - """ - NL - - """ - Norway. - """ - NO - - """ - Nepal. - """ - NP - - """ - Nauru. - """ - NR - - """ - Niue. - """ - NU - - """ - New Zealand. - """ - NZ - - """ - Oman. - """ - OM - - """ - Panama. - """ - PA - - """ - Peru. - """ - PE - - """ - French Polynesia. - """ - PF - - """ - Papua New Guinea. - """ - PG - - """ - Philippines. - """ - PH - - """ - Pakistan. - """ - PK - - """ - Poland. - """ - PL - - """ - St. Pierre & Miquelon. - """ - PM - - """ - Pitcairn Islands. - """ - PN - - """ - Palestinian Territories. - """ - PS - - """ - Portugal. - """ - PT - - """ - Paraguay. - """ - PY - - """ - Qatar. - """ - QA - - """ - Réunion. - """ - RE - - """ - Romania. - """ - RO - - """ - Serbia. - """ - RS - - """ - Russia. - """ - RU - - """ - Rwanda. - """ - RW - - """ - Saudi Arabia. - """ - SA - - """ - Solomon Islands. - """ - SB - - """ - Seychelles. - """ - SC - - """ - Sudan. - """ - SD - - """ - Sweden. - """ - SE - - """ - Singapore. - """ - SG - - """ - St. Helena. - """ - SH - - """ - Slovenia. - """ - SI - - """ - Svalbard & Jan Mayen. - """ - SJ - - """ - Slovakia. - """ - SK - - """ - Sierra Leone. - """ - SL - - """ - San Marino. - """ - SM - - """ - Senegal. - """ - SN - - """ - Somalia. - """ - SO - - """ - Suriname. - """ - SR - - """ - South Sudan. - """ - SS - - """ - São Tomé & Príncipe. - """ - ST - - """ - El Salvador. - """ - SV - - """ - Sint Maarten. - """ - SX - - """ - Syria. - """ - SY - - """ - Eswatini. - """ - SZ - - """ - Tristan da Cunha. - """ - TA - - """ - Turks & Caicos Islands. - """ - TC - - """ - Chad. - """ - TD - - """ - French Southern Territories. - """ - TF - - """ - Togo. - """ - TG - - """ - Thailand. - """ - TH - - """ - Tajikistan. - """ - TJ - - """ - Tokelau. - """ - TK - - """ - Timor-Leste. - """ - TL - - """ - Turkmenistan. - """ - TM - - """ - Tunisia. - """ - TN - - """ - Tonga. - """ - TO - - """ - Turkey. - """ - TR - - """ - Trinidad & Tobago. - """ - TT - - """ - Tuvalu. - """ - TV - - """ - Taiwan. - """ - TW - - """ - Tanzania. - """ - TZ - - """ - Ukraine. - """ - UA - - """ - Uganda. - """ - UG - - """ - U.S. Outlying Islands. - """ - UM - - """ - United States. - """ - US - - """ - Uruguay. - """ - UY - - """ - Uzbekistan. - """ - UZ - - """ - Vatican City. - """ - VA - - """ - St. Vincent & Grenadines. - """ - VC - - """ - Venezuela. - """ - VE - - """ - British Virgin Islands. - """ - VG - - """ - Vietnam. - """ - VN - - """ - Vanuatu. - """ - VU - - """ - Wallis & Futuna. - """ - WF - - """ - Samoa. - """ - WS - - """ - Kosovo. - """ - XK - - """ - Yemen. - """ - YE - - """ - Mayotte. - """ - YT - - """ - South Africa. - """ - ZA - - """ - Zambia. - """ - ZM - - """ - Zimbabwe. - """ - ZW - - """ - Unknown Region. - """ - ZZ -} - -""" -The three-letter currency codes that represent the world currencies used in -stores. These include standard ISO 4217 codes, legacy codes, -and non-standard codes. -""" -enum CurrencyCode { - """ - United Arab Emirates Dirham (AED). - """ - AED - - """ - Afghan Afghani (AFN). - """ - AFN - - """ - Albanian Lek (ALL). - """ - ALL - - """ - Armenian Dram (AMD). - """ - AMD - - """ - Netherlands Antillean Guilder. - """ - ANG - - """ - Angolan Kwanza (AOA). - """ - AOA - - """ - Argentine Pesos (ARS). - """ - ARS - - """ - Australian Dollars (AUD). - """ - AUD - - """ - Aruban Florin (AWG). - """ - AWG - - """ - Azerbaijani Manat (AZN). - """ - AZN - - """ - Bosnia and Herzegovina Convertible Mark (BAM). - """ - BAM - - """ - Barbadian Dollar (BBD). - """ - BBD - - """ - Bangladesh Taka (BDT). - """ - BDT - - """ - Bulgarian Lev (BGN). - """ - BGN - - """ - Bahraini Dinar (BHD). - """ - BHD - - """ - Burundian Franc (BIF). - """ - BIF - - """ - Bermudian Dollar (BMD). - """ - BMD - - """ - Brunei Dollar (BND). - """ - BND - - """ - Bolivian Boliviano (BOB). - """ - BOB - - """ - Brazilian Real (BRL). - """ - BRL - - """ - Bahamian Dollar (BSD). - """ - BSD - - """ - Bhutanese Ngultrum (BTN). - """ - BTN - - """ - Botswana Pula (BWP). - """ - BWP - - """ - Belarusian Ruble (BYN). - """ - BYN - - """ - Belarusian Ruble (BYR). - """ - BYR @deprecated(reason: "`BYR` is deprecated. Use `BYN` available from version `2021-01` onwards instead.") - - """ - Belize Dollar (BZD). - """ - BZD - - """ - Canadian Dollars (CAD). - """ - CAD - - """ - Congolese franc (CDF). - """ - CDF - - """ - Swiss Francs (CHF). - """ - CHF - - """ - Chilean Peso (CLP). - """ - CLP - - """ - Chinese Yuan Renminbi (CNY). - """ - CNY - - """ - Colombian Peso (COP). - """ - COP - - """ - Costa Rican Colones (CRC). - """ - CRC - - """ - Cape Verdean escudo (CVE). - """ - CVE - - """ - Czech Koruny (CZK). - """ - CZK - - """ - Djiboutian Franc (DJF). - """ - DJF - - """ - Danish Kroner (DKK). - """ - DKK - - """ - Dominican Peso (DOP). - """ - DOP - - """ - Algerian Dinar (DZD). - """ - DZD - - """ - Egyptian Pound (EGP). - """ - EGP - - """ - Eritrean Nakfa (ERN). - """ - ERN - - """ - Ethiopian Birr (ETB). - """ - ETB - - """ - Euro (EUR). - """ - EUR - - """ - Fijian Dollars (FJD). - """ - FJD - - """ - Falkland Islands Pounds (FKP). - """ - FKP - - """ - United Kingdom Pounds (GBP). - """ - GBP - - """ - Georgian Lari (GEL). - """ - GEL - - """ - Ghanaian Cedi (GHS). - """ - GHS - - """ - Gibraltar Pounds (GIP). - """ - GIP - - """ - Gambian Dalasi (GMD). - """ - GMD - - """ - Guinean Franc (GNF). - """ - GNF - - """ - Guatemalan Quetzal (GTQ). - """ - GTQ - - """ - Guyanese Dollar (GYD). - """ - GYD - - """ - Hong Kong Dollars (HKD). - """ - HKD - - """ - Honduran Lempira (HNL). - """ - HNL - - """ - Croatian Kuna (HRK). - """ - HRK - - """ - Haitian Gourde (HTG). - """ - HTG - - """ - Hungarian Forint (HUF). - """ - HUF - - """ - Indonesian Rupiah (IDR). - """ - IDR - - """ - Israeli New Shekel (NIS). - """ - ILS - - """ - Indian Rupees (INR). - """ - INR - - """ - Iraqi Dinar (IQD). - """ - IQD - - """ - Iranian Rial (IRR). - """ - IRR - - """ - Icelandic Kronur (ISK). - """ - ISK - - """ - Jersey Pound. - """ - JEP - - """ - Jamaican Dollars (JMD). - """ - JMD - - """ - Jordanian Dinar (JOD). - """ - JOD - - """ - Japanese Yen (JPY). - """ - JPY - - """ - Kenyan Shilling (KES). - """ - KES - - """ - Kyrgyzstani Som (KGS). - """ - KGS - - """ - Cambodian Riel. - """ - KHR - - """ - Kiribati Dollar (KID). - """ - KID - - """ - Comorian Franc (KMF). - """ - KMF - - """ - South Korean Won (KRW). - """ - KRW - - """ - Kuwaiti Dinar (KWD). - """ - KWD - - """ - Cayman Dollars (KYD). - """ - KYD - - """ - Kazakhstani Tenge (KZT). - """ - KZT - - """ - Laotian Kip (LAK). - """ - LAK - - """ - Lebanese Pounds (LBP). - """ - LBP - - """ - Sri Lankan Rupees (LKR). - """ - LKR - - """ - Liberian Dollar (LRD). - """ - LRD - - """ - Lesotho Loti (LSL). - """ - LSL - - """ - Lithuanian Litai (LTL). - """ - LTL - - """ - Latvian Lati (LVL). - """ - LVL - - """ - Libyan Dinar (LYD). - """ - LYD - - """ - Moroccan Dirham. - """ - MAD - - """ - Moldovan Leu (MDL). - """ - MDL - - """ - Malagasy Ariary (MGA). - """ - MGA - - """ - Macedonia Denar (MKD). - """ - MKD - - """ - Burmese Kyat (MMK). - """ - MMK - - """ - Mongolian Tugrik. - """ - MNT - - """ - Macanese Pataca (MOP). - """ - MOP - - """ - Mauritanian Ouguiya (MRU). - """ - MRU - - """ - Mauritian Rupee (MUR). - """ - MUR - - """ - Maldivian Rufiyaa (MVR). - """ - MVR - - """ - Malawian Kwacha (MWK). - """ - MWK - - """ - Mexican Pesos (MXN). - """ - MXN - - """ - Malaysian Ringgits (MYR). - """ - MYR - - """ - Mozambican Metical. - """ - MZN - - """ - Namibian Dollar. - """ - NAD - - """ - Nigerian Naira (NGN). - """ - NGN - - """ - Nicaraguan Córdoba (NIO). - """ - NIO - - """ - Norwegian Kroner (NOK). - """ - NOK - - """ - Nepalese Rupee (NPR). - """ - NPR - - """ - New Zealand Dollars (NZD). - """ - NZD - - """ - Omani Rial (OMR). - """ - OMR - - """ - Panamian Balboa (PAB). - """ - PAB - - """ - Peruvian Nuevo Sol (PEN). - """ - PEN - - """ - Papua New Guinean Kina (PGK). - """ - PGK - - """ - Philippine Peso (PHP). - """ - PHP - - """ - Pakistani Rupee (PKR). - """ - PKR - - """ - Polish Zlotych (PLN). - """ - PLN - - """ - Paraguayan Guarani (PYG). - """ - PYG - - """ - Qatari Rial (QAR). - """ - QAR - - """ - Romanian Lei (RON). - """ - RON - - """ - Serbian dinar (RSD). - """ - RSD - - """ - Russian Rubles (RUB). - """ - RUB - - """ - Rwandan Franc (RWF). - """ - RWF - - """ - Saudi Riyal (SAR). - """ - SAR - - """ - Solomon Islands Dollar (SBD). - """ - SBD - - """ - Seychellois Rupee (SCR). - """ - SCR - - """ - Sudanese Pound (SDG). - """ - SDG - - """ - Swedish Kronor (SEK). - """ - SEK - - """ - Singapore Dollars (SGD). - """ - SGD - - """ - Saint Helena Pounds (SHP). - """ - SHP - - """ - Sierra Leonean Leone (SLL). - """ - SLL - - """ - Somali Shilling (SOS). - """ - SOS - - """ - Surinamese Dollar (SRD). - """ - SRD - - """ - South Sudanese Pound (SSP). - """ - SSP - - """ - Sao Tome And Principe Dobra (STD). - """ - STD @deprecated(reason: "`STD` is deprecated. Use `STN` available from version `2022-07` onwards instead.") - - """ - Sao Tome And Principe Dobra (STN). - """ - STN - - """ - Syrian Pound (SYP). - """ - SYP - - """ - Swazi Lilangeni (SZL). - """ - SZL - - """ - Thai baht (THB). - """ - THB - - """ - Tajikistani Somoni (TJS). - """ - TJS - - """ - Turkmenistani Manat (TMT). - """ - TMT - - """ - Tunisian Dinar (TND). - """ - TND - - """ - Tongan Pa'anga (TOP). - """ - TOP - - """ - Turkish Lira (TRY). - """ - TRY - - """ - Trinidad and Tobago Dollars (TTD). - """ - TTD - - """ - Taiwan Dollars (TWD). - """ - TWD - - """ - Tanzanian Shilling (TZS). - """ - TZS - - """ - Ukrainian Hryvnia (UAH). - """ - UAH - - """ - Ugandan Shilling (UGX). - """ - UGX - - """ - United States Dollars (USD). - """ - USD - - """ - Uruguayan Pesos (UYU). - """ - UYU - - """ - Uzbekistan som (UZS). - """ - UZS - - """ - Venezuelan Bolivares (VED). - """ - VED - - """ - Venezuelan Bolivares (VEF). - """ - VEF @deprecated(reason: "`VEF` is deprecated. Use `VES` available from version `2020-10` onwards instead.") - - """ - Venezuelan Bolivares (VES). - """ - VES - - """ - Vietnamese đồng (VND). - """ - VND - - """ - Vanuatu Vatu (VUV). - """ - VUV - - """ - Samoan Tala (WST). - """ - WST - - """ - Central African CFA Franc (XAF). - """ - XAF - - """ - East Caribbean Dollar (XCD). - """ - XCD - - """ - West African CFA franc (XOF). - """ - XOF - - """ - CFP Franc (XPF). - """ - XPF - - """ - Unrecognized currency. - """ - XXX - - """ - Yemeni Rial (YER). - """ - YER - - """ - South African Rand (ZAR). - """ - ZAR - - """ - Zambian Kwacha (ZMW). - """ - ZMW -} - -""" -A custom product. -""" -type CustomProduct { - """ - Whether the merchandise is a gift card. - """ - isGiftCard: Boolean! - - """ - Whether the merchandise requires shipping. - """ - requiresShipping: Boolean! - - """ - The weight of the product variant in the unit system specified with `weight_unit`. - """ - weight: Float - - """ - Unit of measurement for weight. - """ - weightUnit: WeightUnit! -} - -""" -Represents a customer with the shop. -""" -type Customer implements HasMetafields { - """ - The total amount of money spent by the customer. Converted from the shop's - currency to the currency of the cart using a market rate. - """ - amountSpent: MoneyV2! - - """ - The customer’s name, email or phone number. - """ - displayName: String! - - """ - The customer’s email address. - """ - email: String - - """ - Whether the customer has any of the given tags. - """ - hasAnyTag( - """ - The tags to search for. - """ - tags: [String!]! = [] - ): Boolean! - - """ - A unique identifier for the customer. - """ - id: ID! - - """ - Returns a metafield by namespace and key that belongs to the resource. - """ - metafield( - """ - The key for the metafield. - """ - key: String! - - """ - The namespace for the metafield. - """ - namespace: String! - ): Metafield - - """ - The number of orders made by the customer. - """ - numberOfOrders: Int! -} - -""" -A signed decimal number, which supports arbitrary precision and is serialized as a string. - -Example values: `"29.99"`, `"29.999"`. -""" -scalar Decimal - -""" -List of different delivery method types. -""" -enum DeliveryMethod { - """ - Local Delivery. - """ - LOCAL - - """ - None. - """ - NONE - - """ - Shipping to a Pickup Point. - """ - PICKUP_POINT - - """ - Local Pickup. - """ - PICK_UP - - """ - Retail. - """ - RETAIL - - """ - Shipping. - """ - SHIPPING -} - -""" -The discount to be applied. -""" -input Discount { - """ - The discount message. - """ - message: String - - """ - The targets of the discount. - """ - targets: [Target!]! - - """ - The value of the discount. - """ - value: Value! -} - -""" -The strategy that's applied to the list of discounts. -""" -enum DiscountApplicationStrategy { - """ - Only apply the first discount with conditions that are satisfied. - """ - FIRST - - """ - Only apply the discount that offers the maximum reduction. - """ - MAXIMUM -} - -""" -A discount wrapper node. -""" -type DiscountNode implements HasMetafields { - """ - Returns a metafield by namespace and key that belongs to the resource. - """ - metafield( - """ - The key for the metafield. - """ - key: String! - - """ - The namespace for the metafield. - """ - namespace: String! - ): Metafield -} - -""" -A fixed amount value. -""" -input FixedAmount { - """ - The fixed amount value of the discount, in the currency of the cart. - - The amount must be greater than or equal to 0. - """ - amount: Decimal! - - """ - Whether to apply the value to each entitled item. - - The default value is `false`, which causes the value to be applied once across the entitled items. - When the value is `true`, the value will be applied to each of the entitled items. - """ - appliesToEachItem: Boolean -} - -""" -The result of the function. -""" -input FunctionResult { - """ - The strategy to apply the list of discounts. - """ - discountApplicationStrategy: DiscountApplicationStrategy! - - """ - The list of discounts to be applied. - """ - discounts: [Discount!]! -} - -""" -Represents information about the metafields associated to the specified resource. -""" -interface HasMetafields { - """ - Returns a metafield by namespace and key that belongs to the resource. - """ - metafield( - """ - The key for the metafield. - """ - key: String! - - """ - The namespace for the metafield. - """ - namespace: String! - ): Metafield -} - -""" -Represents a unique identifier, often used to refetch an object. -The ID type appears in a JSON response as a String, but it is not intended to be human-readable. - -Example value: `"gid://shopify/Product/10079785100"` -""" -scalar ID - -""" -The input object for the function. -""" -type Input { - """ - The cart to discount. - """ - cart: Cart! - - """ - The discount node executing the function. - """ - discountNode: DiscountNode! - - """ - The localization of the function execution context. - """ - localization: Localization! - - """ - The conversion rate between the shop's currency and the currency of the cart. - """ - presentmentCurrencyRate: Decimal! -} - -""" -A language. -""" -type Language { - """ - The ISO code. - """ - isoCode: LanguageCode! -} - -""" -ISO 639-1 language codes supported by Shopify. -""" -enum LanguageCode { - """ - Afrikaans. - """ - AF - - """ - Akan. - """ - AK - - """ - Amharic. - """ - AM - - """ - Arabic. - """ - AR - - """ - Assamese. - """ - AS - - """ - Azerbaijani. - """ - AZ - - """ - Belarusian. - """ - BE - - """ - Bulgarian. - """ - BG - - """ - Bambara. - """ - BM - - """ - Bangla. - """ - BN - - """ - Tibetan. - """ - BO - - """ - Breton. - """ - BR - - """ - Bosnian. - """ - BS - - """ - Catalan. - """ - CA - - """ - Chechen. - """ - CE - - """ - Czech. - """ - CS - - """ - Church Slavic. - """ - CU - - """ - Welsh. - """ - CY - - """ - Danish. - """ - DA - - """ - German. - """ - DE - - """ - Dzongkha. - """ - DZ - - """ - Ewe. - """ - EE - - """ - Greek. - """ - EL - - """ - English. - """ - EN - - """ - Esperanto. - """ - EO - - """ - Spanish. - """ - ES - - """ - Estonian. - """ - ET - - """ - Basque. - """ - EU - - """ - Persian. - """ - FA - - """ - Fulah. - """ - FF - - """ - Finnish. - """ - FI - - """ - Faroese. - """ - FO - - """ - French. - """ - FR - - """ - Western Frisian. - """ - FY - - """ - Irish. - """ - GA - - """ - Scottish Gaelic. - """ - GD - - """ - Galician. - """ - GL - - """ - Gujarati. - """ - GU - - """ - Manx. - """ - GV - - """ - Hausa. - """ - HA - - """ - Hebrew. - """ - HE - - """ - Hindi. - """ - HI - - """ - Croatian. - """ - HR - - """ - Hungarian. - """ - HU - - """ - Armenian. - """ - HY - - """ - Interlingua. - """ - IA - - """ - Indonesian. - """ - ID - - """ - Igbo. - """ - IG - - """ - Sichuan Yi. - """ - II - - """ - Icelandic. - """ - IS - - """ - Italian. - """ - IT - - """ - Japanese. - """ - JA - - """ - Javanese. - """ - JV - - """ - Georgian. - """ - KA - - """ - Kikuyu. - """ - KI - - """ - Kazakh. - """ - KK - - """ - Kalaallisut. - """ - KL - - """ - Khmer. - """ - KM - - """ - Kannada. - """ - KN - - """ - Korean. - """ - KO - - """ - Kashmiri. - """ - KS - - """ - Kurdish. - """ - KU - - """ - Cornish. - """ - KW - - """ - Kyrgyz. - """ - KY - - """ - Luxembourgish. - """ - LB - - """ - Ganda. - """ - LG - - """ - Lingala. - """ - LN - - """ - Lao. - """ - LO - - """ - Lithuanian. - """ - LT - - """ - Luba-Katanga. - """ - LU - - """ - Latvian. - """ - LV - - """ - Malagasy. - """ - MG - - """ - Māori. - """ - MI - - """ - Macedonian. - """ - MK - - """ - Malayalam. - """ - ML - - """ - Mongolian. - """ - MN - - """ - Marathi. - """ - MR - - """ - Malay. - """ - MS - - """ - Maltese. - """ - MT - - """ - Burmese. - """ - MY - - """ - Norwegian (Bokmål). - """ - NB - - """ - North Ndebele. - """ - ND - - """ - Nepali. - """ - NE - - """ - Dutch. - """ - NL - - """ - Norwegian Nynorsk. - """ - NN - - """ - Norwegian. - """ - NO - - """ - Oromo. - """ - OM - - """ - Odia. - """ - OR - - """ - Ossetic. - """ - OS - - """ - Punjabi. - """ - PA - - """ - Polish. - """ - PL - - """ - Pashto. - """ - PS - - """ - Portuguese. - """ - PT - - """ - Portuguese (Brazil). - """ - PT_BR - - """ - Portuguese (Portugal). - """ - PT_PT - - """ - Quechua. - """ - QU - - """ - Romansh. - """ - RM - - """ - Rundi. - """ - RN - - """ - Romanian. - """ - RO - - """ - Russian. - """ - RU - - """ - Kinyarwanda. - """ - RW - - """ - Sindhi. - """ - SD - - """ - Northern Sami. - """ - SE - - """ - Sango. - """ - SG - - """ - Sinhala. - """ - SI - - """ - Slovak. - """ - SK - - """ - Slovenian. - """ - SL - - """ - Shona. - """ - SN - - """ - Somali. - """ - SO - - """ - Albanian. - """ - SQ - - """ - Serbian. - """ - SR - - """ - Sundanese. - """ - SU - - """ - Swedish. - """ - SV - - """ - Swahili. - """ - SW - - """ - Tamil. - """ - TA - - """ - Telugu. - """ - TE - - """ - Tajik. - """ - TG - - """ - Thai. - """ - TH - - """ - Tigrinya. - """ - TI - - """ - Turkmen. - """ - TK - - """ - Tongan. - """ - TO - - """ - Turkish. - """ - TR - - """ - Tatar. - """ - TT - - """ - Uyghur. - """ - UG - - """ - Ukrainian. - """ - UK - - """ - Urdu. - """ - UR - - """ - Uzbek. - """ - UZ - - """ - Vietnamese. - """ - VI - - """ - Volapük. - """ - VO - - """ - Wolof. - """ - WO - - """ - Xhosa. - """ - XH - - """ - Yiddish. - """ - YI - - """ - Yoruba. - """ - YO - - """ - Chinese. - """ - ZH - - """ - Chinese (Simplified). - """ - ZH_CN - - """ - Chinese (Traditional). - """ - ZH_TW - - """ - Zulu. - """ - ZU -} - -""" -Information about the localized experiences configured for the shop. -""" -type Localization { - """ - The country of the active localized experience. - """ - country: Country! - - """ - The language of the active localized experience. - """ - language: Language! -} - -""" -Represents a mailing address. -""" -type MailingAddress { - """ - The first line of the address. Typically the street address or PO Box number. - """ - address1: String - - """ - The second line of the address. Typically the number of the apartment, suite, or unit. - """ - address2: String - - """ - The name of the city, district, village, or town. - """ - city: String - - """ - The name of the customer's company or organization. - """ - company: String - - """ - The two-letter code for the country of the address. For example, US. - """ - countryCode: CountryCode - - """ - The first name of the customer. - """ - firstName: String - - """ - The last name of the customer. - """ - lastName: String - - """ - The full name of the customer, based on firstName and lastName. - """ - name: String - - """ - A unique phone number for the customer. Formatted using E.164 standard. For example, +16135551111. - """ - phone: String - - """ - The two-letter code for the region. For example, ON. - """ - provinceCode: String - - """ - The zip or postal code of the address. - """ - zip: String -} - -""" -The merchandise to be purchased at checkout. -""" -union Merchandise = CustomProduct | ProductVariant - -""" -[Metafields](https://shopify.dev/apps/metafields) -enable you to attach additional information to a -Shopify resource, such as a [Product](https://shopify.dev/api/admin-graphql/latest/objects/product) -or a [Collection](https://shopify.dev/api/admin-graphql/latest/objects/collection). -For more information about the Shopify resources that you can attach metafields to, refer to -[HasMetafields](https://shopify.dev/api/admin/graphql/reference/common-objects/HasMetafields). -""" -type Metafield { - """ - The type of data that the metafield stores in the `value` field. - Refer to the list of [supported types](https://shopify.dev/apps/metafields/types). - """ - type: String! - - """ - The data to store in the metafield. The data is always stored as a string, regardless of the metafield's type. - """ - value: String! -} - -""" -A monetary value with currency. -""" -type MoneyV2 { - """ - Decimal money amount. - """ - amount: Decimal! - - """ - Currency of the money. - """ - currencyCode: CurrencyCode! -} - -""" -The root mutation for the API. -""" -type MutationRoot { - """ - Handles the function result. - """ - handleResult( - """ - The result of the function. - """ - result: FunctionResult! - ): Void! -} - -""" -A percentage value. -""" -input Percentage { - """ - The percentage value. - - The value is validated against: >= 0. - """ - value: Decimal! -} - -""" -Represents a product. -""" -type Product implements HasMetafields { - """ - A unique human-friendly string of the product's title. - """ - handle: String! - - """ - Whether the product has any of the given tags. - """ - hasAnyTag( - """ - The tags to search for. - """ - tags: [String!]! = [] - ): Boolean! - - """ - A globally-unique identifier. - """ - id: ID! - - """ - Whether the product has any of the given collection. - """ - inAnyCollection( - """ - The collections to search for. - """ - ids: [ID!]! = [] - ): Boolean! - - """ - Whether the product is a gift card. - """ - isGiftCard: Boolean! - - """ - Returns a metafield by namespace and key that belongs to the resource. - """ - metafield( - """ - The key for the metafield. - """ - key: String! - - """ - The namespace for the metafield. - """ - namespace: String! - ): Metafield - - """ - The product type specified by the merchant. - """ - productType: String - - """ - The name of the product's vendor. - """ - vendor: String -} - -""" -Represents a product variant. -""" -type ProductVariant implements HasMetafields { - """ - A globally-unique identifier. - """ - id: ID! - - """ - Returns a metafield by namespace and key that belongs to the resource. - """ - metafield( - """ - The key for the metafield. - """ - key: String! - - """ - The namespace for the metafield. - """ - namespace: String! - ): Metafield - - """ - The product that this variant belongs to. - """ - product: Product! - - """ - Whether the merchandise requires shipping. - """ - requiresShipping: Boolean! - - """ - An identifier for the product variant in the shop. Required in order to connect to a fulfillment service. - """ - sku: String - - """ - The weight of the product variant in the unit system specified with `weight_unit`. - """ - weight: Float - - """ - Unit of measurement for weight. - """ - weightUnit: WeightUnit! -} - -""" -The target product variant. -""" -input ProductVariantTarget { - """ - The ID of the target product variant. - """ - id: ID! - - """ - The number of line items that are being discounted. - The default value is `null`, which represents the quantity of the matching line items. - - The value is validated against: > 0. - """ - quantity: Int -} - -""" -The target of the discount. -""" -input Target @oneOf { - """ - The target product variant. - """ - productVariant: ProductVariantTarget -} - -""" -The value of the discount. -""" -input Value @oneOf { - """ - A fixed amount value. - """ - fixedAmount: FixedAmount - - """ - A percentage value. - """ - percentage: Percentage -} - -""" -A void type that can be used to return a null value from a mutation. -""" -scalar Void - -""" -Units of measurement for weight. -""" -enum WeightUnit { - """ - Metric system unit of mass. - """ - GRAMS - - """ - 1 kilogram equals 1000 grams. - """ - KILOGRAMS - - """ - Imperial system unit of mass. - """ - OUNCES - - """ - 1 pound equals 16 ounces. - """ - POUNDS -} diff --git a/example/src/main.rs b/example/src/main.rs deleted file mode 100644 index 78f2e0c..0000000 --- a/example/src/main.rs +++ /dev/null @@ -1,70 +0,0 @@ -use shopify_function::prelude::*; -use shopify_function::Result; - -use serde::{Deserialize, Serialize}; - -generate_types!( - query_path = "./input.graphql", - schema_path = "./schema.graphql" -); - -#[derive(Serialize, Deserialize, Default, PartialEq)] -struct Config { - pub quantity: i64, - pub percentage: f64, -} - -#[shopify_function] -fn function(input: input::ResponseData) -> Result { - let config: Config = input - .discount_node - .metafield - .as_ref() - .map(|m| serde_json::from_str::(m.value.as_str())) - .transpose()? - .unwrap_or_default(); - - let cart_lines = input.cart.lines; - - if cart_lines.is_empty() || config.percentage == 0.0 { - return Ok(output::FunctionResult { - discount_application_strategy: output::DiscountApplicationStrategy::FIRST, - discounts: vec![], - }); - } - - let mut targets = vec![]; - for line in cart_lines { - if line.quantity >= config.quantity { - targets.push(output::Target::ProductVariant( - output::ProductVariantTarget { - id: match line.merchandise { - input::InputCartLinesMerchandise::ProductVariant(variant) => variant.id, - _ => continue, - }, - quantity: None, - }, - )); - } - } - - if targets.is_empty() { - return Ok(output::FunctionResult { - discount_application_strategy: output::DiscountApplicationStrategy::FIRST, - discounts: vec![], - }); - } - Ok(output::FunctionResult { - discounts: vec![output::Discount { - message: None, - targets, - value: output::Value::Percentage(output::Percentage { - value: Decimal(config.percentage), - }), - }], - discount_application_strategy: output::DiscountApplicationStrategy::FIRST, - }) -} - -#[cfg(test)] -mod tests; diff --git a/example/src/tests.rs b/example/src/tests.rs deleted file mode 100644 index 6de906b..0000000 --- a/example/src/tests.rs +++ /dev/null @@ -1,112 +0,0 @@ -use super::*; -use shopify_function::{run_function_with_input, Result}; - -#[test] -fn test_discount_with_no_configuration() -> Result<()> { - let result = run_function_with_input( - function, - r#" - { - "cart": { - "lines": [ - { - "cost": { - "totalAmount": { - "amount": "0" - } - }, - "merchandise": { - "__typename": "ProductVariant", - "id": "gid://shopify/ProductVariant/0" - }, - "quantity": 5 - }, - { - "cost": { - "totalAmount": { - "amount": "0" - } - }, - "merchandise": { - "__typename": "ProductVariant", - "id": "gid://shopify/ProductVariant/1" - }, - "quantity": 1 - } - ] - }, - "discountNode": { - "metafield": null - } - } - "#, - )?; - let expected = crate::output::FunctionResult { - discounts: vec![], - discount_application_strategy: crate::output::DiscountApplicationStrategy::FIRST, - }; - assert_eq!(result, expected); - Ok(()) -} - -#[test] -fn test_discount_with_configuration() -> Result<()> { - let result = run_function_with_input( - function, - r#" - { - "cart": { - "lines": [ - { - "cost": { - "totalAmount": { - "amount": "0" - } - }, - "merchandise": { - "__typename": "ProductVariant", - "id": "gid://shopify/ProductVariant/0" - }, - "quantity": 5 - }, - { - "cost": { - "totalAmount": { - "amount": "10" - } - }, - "merchandise": { - "__typename": "ProductVariant", - "id": "gid://shopify/ProductVariant/1" - }, - "quantity": 1 - } - ] - }, - "discountNode": { - "metafield": { - "value": "{\"quantity\": 5, \"percentage\": 10}" - } - } - } - "#, - )?; - let expected = crate::output::FunctionResult { - discounts: vec![crate::output::Discount { - message: None, - targets: vec![crate::output::Target::ProductVariant( - output::ProductVariantTarget { - id: "gid://shopify/ProductVariant/0".to_string(), - quantity: None, - }, - )], - value: crate::output::Value::Percentage(output::Percentage { - value: Decimal(10.0), - }), - }], - discount_application_strategy: crate::output::DiscountApplicationStrategy::FIRST, - }; - - assert_eq!(result, expected); - Ok(()) -} diff --git a/example_with_targets/Cargo.toml b/example_with_targets/Cargo.toml deleted file mode 100644 index cc1c7c3..0000000 --- a/example_with_targets/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "example_with_targets" -version = "1.0.0" -edition = "2021" -license = "MIT" - -[dependencies] -shopify_function = { path = "../shopify_function" } -serde = { version = "1.0.215", features = ["derive"] } -serde_json = "1.0" -graphql_client = "0.14.0" -graphql_client_codegen = "0.14.0" diff --git a/example_with_targets/README.md b/example_with_targets/README.md deleted file mode 100644 index 14fd75e..0000000 --- a/example_with_targets/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# Shopify Rust function example with targets - -This is an example of how to use the [Shopify Function Rust crate][crate] with the `shopify_function_target` macro to write a Shopify Function with multiple API targets. - -## Example API - -Target | Handle (`snake_case`) | GraphQL mutation --- | -- | -- -`example.target-a` | `target_a` | `targetA(result: FunctionTargetAResult!)` -`example.target-b` | `target_b` | `targetB(result: FunctionTargetBResult!)` - -### `schema.graphql` - -```graphql -""" -The input object for the function. -""" -type Input { - id: ID! - num: Int - name: String - targetAResult: Int @restrictTarget(only: ["example.target-b"]) -} - -""" -The root mutation for the API. -""" -type MutationRoot { - """ - The function for API target A. - """ - targetA( - """ - The result of calling the function for API target A. - """ - result: FunctionTargetAResult! - ): Void! - - """ - The function for API target B. - """ - targetB( - """ - The result of calling the function for API target B. - """ - result: FunctionTargetBResult! - ): Void! -} - -""" -The result of API target A. -""" -input FunctionTargetAResult { - status: Int -} - -""" -The result of API target B. -""" -input FunctionTargetBResult { - name: String -} -``` - -## `shopify.function.extension.toml` - -```toml -[[targeting]] -target = "example.target-a" -input_query = "a.graphql" -export = "target_a" - -[[targeting]] -target = "example.target-b" -input_query = "b.graphql" -export = "function_b" -``` - -- `target`: The API-specific handle for the target implemented by the Wasm function. -- `input_query`: The path to the target-specific input query file. -- `export` (optional): The name of the Wasm function export to run. - - default: The target handle as `snake_case`. - -## `shopify_function_target` usage - -### Arguments - -- `query_path`: A path to a GraphQL query, whose result will be used - as the input for the function invocation. The query MUST be named "Input". -- `schema_path`: A path to Shopify's GraphQL schema definition. Use the CLI - to download a fresh copy. -- `target` (optional): The API-specific handle for the target if the function name does not match the target handle as `snake_case`. -- `module_name` (optional): The name of the generated module. - - default: The target handle as `snake_case` -- `extern_enums` (optional): A list of Enums for which an external type should be used. - For those, code generation will be skipped. This is useful for large enums - which can increase binary size, or for enums shared between multiple targets. - Example: `extern_enums = ["LanguageCode"]` - - default: `["LanguageCode", "CountryCode", "CurrencyCode"]` - -### `src/lib.rs` - -```rust -#[shopify_function_target( - // Implicit target = "example.target-a" - // Implicit generated module name = "target_a" - query_path = "a.graphql", - schema_path = "schema.graphql" -)] -fn target_a( - _input: target_a::input::ResponseData, -) -> Result { - Ok(target_a::output::FunctionTargetAResult { status: Some(200) }) -} - -#[shopify_function_target( - // Explicit target if function name does not match target handle - target = "example.target-b", - // Override generated module name - module_name = "mod_b", - query_path = "b.graphql", - schema_path = "schema.graphql" -)] -fn function_b(input: mod_b::input::ResponseData) -> Result { - Ok(mod_b::output::FunctionTargetBResult { - name: Some(format!("new name: \"{}\"", input.id)), - }) -} -``` - -### Generated code - -The `shopify_function_target` macro uses `generate_types` and `shopify_function` to generate a module (optionally using `module_name`) containing: - -- `input` - - `ResponseData` for the target-specific input query -- `output` - - a target-specific result type, e.g. `FunctionTargetAResult` -- `export`: The function exported to the Wasm module using the Rust function name, which must match the export specified for the target in `shopify.function.extension.toml` - -The generated types can be viewed using the instructions in the `shopify_function` crate `README`. - -#### `*.output.graphql` - -Each target will have an `.output.graphql` file prefixed with the target handle as `snake_case`. This file is used to generate the target-specific `output` types, and can be added to `.gitignore`. - -Target handle (`snake_case`) | Output file | GraphQL mutation --- | -- | -- -`target_a` | `.target_a.output.graphql` | `mutation Output($result: FunctionTargetAResult!) { targetA(result: $result) }` -`target_b` | `.target_b.output.graphql` | `mutation Output($result: FunctionTargetBResult!) { targetB(result: $result) }` - -If the Rust function name does not match the target handle as `snake_case`, the `target` argument must be provided to `shopify_function_target` to generate the `output` types. - -[crate]: https://crates.io/crates/shopify-function diff --git a/example_with_targets/shopify.function.extension.toml b/example_with_targets/shopify.function.extension.toml deleted file mode 100644 index c2a5fc6..0000000 --- a/example_with_targets/shopify.function.extension.toml +++ /dev/null @@ -1,16 +0,0 @@ -name = "example-with-targets" -type = "test_api" -api_version = "unstable" - -[build] -command = "cargo wasi build --release" -path = "target/wasm32-wasi/release/example-with-targets.wasm" - -[[targeting]] -target = "test.target-a" -input_query = "a.graphql" - -[[targeting]] -target = "test.target-b" -export = "function_b" -input_query = "b.graphql" diff --git a/example_with_targets/src/lib.rs b/example_with_targets/src/lib.rs deleted file mode 100644 index a87d78a..0000000 --- a/example_with_targets/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -use shopify_function::prelude::*; -use shopify_function::Result; - -#[shopify_function_target( - // Implicit target = "example.target-a" - // Implicit generated module name = "target_a" - query_path = "a.graphql", - schema_path = "schema.graphql" -)] -fn target_a( - _input: target_a::input::ResponseData, -) -> Result { - Ok(target_a::output::FunctionTargetAResult { status: Some(200) }) -} - -#[shopify_function_target( - // Explicit target if function name does not match target handle - target = "example.target-b", - // Override generated module name - module_name = "mod_b", - query_path = "b.graphql", - schema_path = "schema.graphql" -)] -fn function_b(input: mod_b::input::ResponseData) -> Result { - Ok(mod_b::output::FunctionTargetBResult { - name: Some(format!("new name: \"{}\"", input.id)), - }) -} - -#[cfg(test)] -mod tests; diff --git a/example_with_targets/src/tests.rs b/example_with_targets/src/tests.rs deleted file mode 100644 index ae1772c..0000000 --- a/example_with_targets/src/tests.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; -use shopify_function::{run_function_with_input, Result}; - -#[test] -fn test_a() -> Result<()> { - let result = run_function_with_input( - target_a, - r#" - { - "id": "gid://shopify/Order/1234567890", - "num": 123, - "name": "test" - } - "#, - )?; - let expected = crate::target_a::output::FunctionTargetAResult { status: Some(200) }; - assert_eq!(result, expected); - Ok(()) -} - -#[test] -fn test_function_b() -> Result<()> { - let result = run_function_with_input( - function_b, - r#" - { - "id": "gid://shopify/Order/1234567890", - "aResult": 200 - } - "#, - )?; - let expected = crate::mod_b::output::FunctionTargetBResult { - name: Some("new name: \"gid://shopify/Order/1234567890\"".to_string()), - }; - - assert_eq!(result, expected); - Ok(()) -} diff --git a/shopify_function/Cargo.toml b/shopify_function/Cargo.toml index 29f5c51..579cb87 100644 --- a/shopify_function/Cargo.toml +++ b/shopify_function/Cargo.toml @@ -7,9 +7,10 @@ license = "MIT" description = "Crate to write Shopify Functions in Rust." [dependencies] -serde = { version = "1.0.215", features = ["derive"] } -serde_json = "1.0" -shopify_function_macro = { version = "0.8.1", path = "../shopify_function_macro" } +serde = { version = "1.0.215", features = ["derive"], optional = true } +serde_json = { version = "1.0", optional = true } +miniserde = { version = "0.1", optional = true } +shopify_function_macro = { version = "0.8.1", path = "../shopify_function_macro", default-features = false } # Use the `small` feature of ryu (transitive dependency through serde_json) # to shave off ~9kb of the Wasm binary size. @@ -17,6 +18,17 @@ shopify_function_macro = { version = "0.8.1", path = "../shopify_function_macro" version = "1" features = ["small"] -[dev-dependencies] -graphql_client = "0.14.0" -graphql_client_codegen = "0.14.0" +[features] +default = ["serde"] +serde = ["dep:serde", "dep:serde_json", "shopify_function_macro/serde"] +miniserde = ["dep:miniserde", "shopify_function_macro/miniserde"] + +[[example]] +name = "example_serde" +path = "examples/example_serde.rs" +required-features = ["serde"] + +[[example]] +name = "example_miniserde" +path = "examples/example_miniserde.rs" +required-features = ["miniserde"] diff --git a/example_with_targets/a.graphql b/shopify_function/examples/a.graphql similarity index 100% rename from example_with_targets/a.graphql rename to shopify_function/examples/a.graphql diff --git a/example_with_targets/b.graphql b/shopify_function/examples/b.graphql similarity index 100% rename from example_with_targets/b.graphql rename to shopify_function/examples/b.graphql diff --git a/shopify_function/examples/example_miniserde.rs b/shopify_function/examples/example_miniserde.rs new file mode 100644 index 0000000..250ea23 --- /dev/null +++ b/shopify_function/examples/example_miniserde.rs @@ -0,0 +1,71 @@ +use shopify_function::prelude::*; +use shopify_function::Result; + +#[typegen("examples/schema.graphql")] +mod schema { + #[query("examples/a.graphql")] + pub mod a {} + + #[query("examples/b.graphql")] + pub mod b {} +} + +#[shopify_function] +fn target_a(_input: schema::a::Input) -> Result { + Ok(schema::FunctionTargetAResult { status: Some(200) }) +} + +#[shopify_function] +fn function_b(input: schema::b::Input) -> Result { + Ok(schema::FunctionTargetBResult { + name: Some(format!("new name: \"{}\"", input.id)), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use shopify_function::{run_function_with_input, Result}; + + #[test] + fn test_a() -> Result<()> { + let result = run_function_with_input( + target_a, + r#" + { + "id": "gid://shopify/Order/1234567890", + "num": 123, + "name": "test", + "date": null, + "dateTime": null, + "dateTimeWithoutTimezone": null, + "timeWithoutTimezone": null + } + "#, + )?; + let expected = crate::schema::FunctionTargetAResult { status: Some(200) }; + assert_eq!(result, expected); + Ok(()) + } + + #[test] + fn test_function_b() -> Result<()> { + let result = run_function_with_input( + function_b, + r#" + { + "id": "gid://shopify/Order/1234567890", + "targetAResult": 200 + } + "#, + )?; + let expected = crate::schema::FunctionTargetBResult { + name: Some("new name: \"gid://shopify/Order/1234567890\"".to_string()), + }; + + assert_eq!(result, expected); + Ok(()) + } +} + +fn main() {} diff --git a/shopify_function/examples/example_serde.rs b/shopify_function/examples/example_serde.rs new file mode 100644 index 0000000..0b3d0bd --- /dev/null +++ b/shopify_function/examples/example_serde.rs @@ -0,0 +1,67 @@ +use shopify_function::prelude::*; +use shopify_function::Result; + +#[typegen("examples/schema.graphql")] +mod schema { + #[query("examples/a.graphql")] + pub mod a {} + + #[query("examples/b.graphql")] + pub mod b {} +} + +#[shopify_function] +fn target_a(_input: schema::a::Input) -> Result { + Ok(schema::FunctionTargetAResult { status: Some(200) }) +} + +#[shopify_function] +fn function_b(input: schema::b::Input) -> Result { + Ok(schema::FunctionTargetBResult { + name: Some(format!("new name: \"{}\"", input.id)), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use shopify_function::{run_function_with_input, Result}; + + #[test] + fn test_a() -> Result<()> { + let result = run_function_with_input( + target_a, + r#" + { + "id": "gid://shopify/Order/1234567890", + "num": 123, + "name": "test" + } + "#, + )?; + let expected = crate::schema::FunctionTargetAResult { status: Some(200) }; + assert_eq!(result, expected); + Ok(()) + } + + #[test] + fn test_function_b() -> Result<()> { + let result = run_function_with_input( + function_b, + r#" + { + "id": "gid://shopify/Order/1234567890", + "aResult": 200 + } + "#, + )?; + let expected = crate::schema::FunctionTargetBResult { + name: Some("new name: \"gid://shopify/Order/1234567890\"".to_string()), + }; + + assert_eq!(result, expected); + Ok(()) + } +} + +fn main() {} diff --git a/example_with_targets/schema.graphql b/shopify_function/examples/schema.graphql similarity index 100% rename from example_with_targets/schema.graphql rename to shopify_function/examples/schema.graphql diff --git a/shopify_function/src/enums.rs b/shopify_function/src/enums.rs deleted file mode 100644 index 33ed864..0000000 --- a/shopify_function/src/enums.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub type CountryCode = String; -pub type CurrencyCode = String; -pub type LanguageCode = String; diff --git a/shopify_function/src/lib.rs b/shopify_function/src/lib.rs index 931f739..db11ba1 100644 --- a/shopify_function/src/lib.rs +++ b/shopify_function/src/lib.rs @@ -16,22 +16,21 @@ //! } //! ``` -pub use shopify_function_macro::{generate_types, shopify_function, shopify_function_target}; +#[cfg(all(feature = "serde", feature = "miniserde"))] +compile_error!("Cannot use both serde and miniserde features"); + +pub use shopify_function_macro::shopify_function; -#[doc(hidden)] -pub mod enums; -/// Only used for struct generation. -#[doc(hidden)] pub mod scalars; pub mod prelude { - pub use crate::enums::*; pub use crate::scalars::*; - pub use shopify_function_macro::{generate_types, shopify_function, shopify_function_target}; + pub use shopify_function_macro::{shopify_function, typegen}; } pub type Result = std::result::Result>; +#[cfg(feature = "serde")] /// Runs the given function `f` with the invocation payload, returning the /// deserialized output. This function is provided as a helper when writing /// tests. @@ -46,5 +45,25 @@ where f(parsed_payload) } +#[cfg(feature = "miniserde")] +/// Runs the given function `f` with the invocation payload, returning the +/// deserialized output. This function is provided as a helper when writing +/// tests. +pub fn run_function_with_input(f: F, payload: &str) -> Result +where + F: Fn(P) -> Result, +{ + let parsed_payload: P = miniserde::json::from_str(payload)?; + f(parsed_payload) +} + +#[cfg(feature = "serde")] +pub use serde; +#[cfg(feature = "serde")] +pub use serde_json; + +#[cfg(feature = "miniserde")] +pub use miniserde; + #[cfg(test)] mod tests {} diff --git a/shopify_function/src/scalars.rs b/shopify_function/src/scalars.rs index 9774bc6..afb9726 100644 --- a/shopify_function/src/scalars.rs +++ b/shopify_function/src/scalars.rs @@ -1,16 +1,3 @@ mod decimal; -pub type Boolean = bool; -pub type Float = f64; -pub type Int = i64; -pub type ID = String; -pub type JSON = serde_json::Value; pub use decimal::Decimal; -pub type Void = (); -pub type URL = String; -pub type Handle = String; - -pub type Date = String; -pub type DateTime = String; -pub type DateTimeWithoutTimezone = String; -pub type TimeWithoutTimezone = String; diff --git a/shopify_function/src/scalars/decimal.rs b/shopify_function/src/scalars/decimal.rs index d296d85..ffaa391 100644 --- a/shopify_function/src/scalars/decimal.rs +++ b/shopify_function/src/scalars/decimal.rs @@ -1,11 +1,11 @@ -use serde::{Deserialize, Serialize}; use std::ops::Deref; /// Convenience wrapper for converting between Shopify's `Decimal` scalar, which /// is serialized as a `String`, and Rust's `f64`. -#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)] -#[serde(try_from = "String")] -#[serde(into = "String")] +#[derive(Debug, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "String"))] +#[cfg_attr(feature = "serde", serde(into = "String"))] pub struct Decimal(pub f64); impl Decimal { @@ -23,6 +23,7 @@ impl Deref for Decimal { } } +#[cfg(feature = "serde")] impl TryFrom for Decimal { type Error = &'static str; @@ -33,12 +34,58 @@ impl TryFrom for Decimal { } } +#[cfg(feature = "miniserde")] +impl TryFrom for Decimal { + type Error = &'static str; + + fn try_from(value: String) -> Result { + miniserde::json::from_str(value.as_str()) + .map(Self) + .map_err(|_| "Error parsing decimal: invalid float literal") + } +} + +#[cfg(feature = "serde")] impl From for String { fn from(value: Decimal) -> Self { ryu::Buffer::new().format(value.0).to_string() } } +#[cfg(feature = "miniserde")] +impl From for String { + fn from(value: Decimal) -> Self { + ryu::Buffer::new().format(value.0).to_string() + } +} + +#[cfg(feature = "miniserde")] +impl miniserde::Serialize for Decimal { + fn begin(&self) -> miniserde::ser::Fragment<'_> { + miniserde::ser::Fragment::Str(miniserde::json::to_string(&self.0).into()) + } +} + +#[cfg(feature = "miniserde")] +miniserde::make_place!(Place); + +#[cfg(feature = "miniserde")] +impl miniserde::de::Visitor for Place { + fn string(&mut self, s: &str) -> miniserde::Result<()> { + self.out = Some(Decimal( + miniserde::json::from_str(s).map_err(|_| miniserde::Error)?, + )); + Ok(()) + } +} + +#[cfg(feature = "miniserde")] +impl miniserde::Deserialize for Decimal { + fn begin(out: &mut Option) -> &mut dyn miniserde::de::Visitor { + Place::new(out) + } +} + impl From for f64 { fn from(value: Decimal) -> Self { value.0 @@ -56,6 +103,7 @@ mod tests { use super::Decimal; #[test] + #[cfg(feature = "serde")] fn test_json_deserialization() { let decimal_value = serde_json::json!("123.4"); let decimal: Decimal = @@ -64,6 +112,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_json_deserialization_error() { let decimal_value = serde_json::json!("123.4.5"); let error = @@ -75,6 +124,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_json_serialization() { let decimal = Decimal(123.4); let json_value = serde_json::to_value(decimal).expect("Error serializing to JSON"); diff --git a/shopify_function/tests/fixtures/schema.graphql b/shopify_function/tests/fixtures/schema.graphql deleted file mode 100644 index 24392be..0000000 --- a/shopify_function/tests/fixtures/schema.graphql +++ /dev/null @@ -1,60 +0,0 @@ -schema { - query: Input - mutation: MutationRoot -} - -""" -Exactly one field of input must be provided, and all others omitted. -""" -directive @oneOf on INPUT_OBJECT - -""" -Represents a unique identifier, often used to refetch an object. -The ID type appears in a JSON response as a String, but it is not intended to be human-readable. - -Example value: `"gid://shopify/Product/10079785100"` -""" -scalar ID - -""" -A void type that can be used to return a null value from a mutation. -""" -scalar Void - -""" -The input object for the function. -""" -type Input { - id: ID! - num: Int - name: String - country: CountryCode -} - -""" -The root mutation for the API. -""" -type MutationRoot { - """ - Handles the function result. - """ - handleResult( - """ - The result of the function. - """ - result: FunctionResult! - ): Void! -} - -""" -The result of the function. -""" -input FunctionResult { - name: String - country: CountryCode -} - -enum CountryCode { - AC - CA -} diff --git a/shopify_function/tests/generate_types.rs b/shopify_function/tests/generate_types.rs deleted file mode 100644 index 137f605..0000000 --- a/shopify_function/tests/generate_types.rs +++ /dev/null @@ -1,21 +0,0 @@ -use shopify_function::prelude::*; - -generate_types!( - query_path = "./tests/fixtures/input.graphql", - schema_path = "./tests/fixtures/schema.graphql" -); - -#[test] -fn test_json_deserialization() { - let input = r#"{ - "id": "gid://shopify/Order/1234567890", - "num": 123, - "name": "test" - }"#; - - let parsed: input::ResponseData = serde_json::from_str(input).unwrap(); - - assert_eq!(parsed.id, "gid://shopify/Order/1234567890"); - assert_eq!(parsed.num, Some(123)); - assert_eq!(parsed.name, Some("test".to_string())); -} diff --git a/shopify_function/tests/shopify_function.rs b/shopify_function/tests/shopify_function.rs deleted file mode 100644 index 77969f9..0000000 --- a/shopify_function/tests/shopify_function.rs +++ /dev/null @@ -1,34 +0,0 @@ -use shopify_function::prelude::*; -use shopify_function::Result; - -const FUNCTION_INPUT: &str = r#"{ - "id": "gid://shopify/Order/1234567890", - "num": 123, - "name": "test", - "country": "CA" -}"#; -static mut FUNCTION_OUTPUT: Vec = vec![]; - -generate_types!( - query_path = "./tests/fixtures/input.graphql", - schema_path = "./tests/fixtures/schema.graphql" -); - -#[test] -fn test_function() { - let expected_result = r#"{"name":"new name: gid://shopify/Order/1234567890","country":"CA"}"#; - main().unwrap(); - let actual_result = std::str::from_utf8(unsafe { FUNCTION_OUTPUT.as_slice() }).unwrap(); - assert_eq!(actual_result, expected_result); -} - -#[shopify_function( - input_stream = std::io::Cursor::new(FUNCTION_INPUT.as_bytes().to_vec()), - output_stream = unsafe { &mut FUNCTION_OUTPUT } -)] -fn my_function(input: input::ResponseData) -> Result { - Ok(output::FunctionResult { - name: Some(format!("new name: {}", input.id)), - country: Some("CA".to_string()), - }) -} diff --git a/shopify_function/tests/shopify_function_target.rs b/shopify_function/tests/shopify_function_target.rs index e2c5eae..9317430 100644 --- a/shopify_function/tests/shopify_function_target.rs +++ b/shopify_function/tests/shopify_function_target.rs @@ -9,28 +9,35 @@ const TARGET_A_INPUT: &str = r#"{ }"#; static mut TARGET_A_OUTPUT: Vec = vec![]; +#[typegen("./tests/fixtures/schema_with_targets.graphql")] +mod schema_with_targets { + #[query("./tests/fixtures/input.graphql")] + pub mod target_a {} + + #[query("./tests/fixtures/b.graphql")] + pub mod target_b {} +} + #[test] fn test_target_a_export() { let expected_result = r#"{"status":200}"#; - target_a::export(); + target_a_export(); let actual_result = std::str::from_utf8(unsafe { TARGET_A_OUTPUT.as_slice() }).unwrap(); assert_eq!(actual_result, expected_result); } -#[shopify_function_target( +#[shopify_function( // Implicit target = "test.target-a" - query_path = "./tests/fixtures/input.graphql", - schema_path = "./tests/fixtures/schema_with_targets.graphql", input_stream = std::io::Cursor::new(TARGET_A_INPUT.as_bytes().to_vec()), output_stream = unsafe { &mut TARGET_A_OUTPUT } )] fn target_a( - input: target_a::input::ResponseData, -) -> Result { + input: schema_with_targets::target_a::Input, +) -> Result { if input.country != Some("CA".to_string()) { panic!("Expected CountryCode to be the CA String") } - Ok(target_a::output::FunctionTargetAResult { status: Some(200) }) + Ok(schema_with_targets::FunctionTargetAResult { status: Some(200) }) } const TARGET_B_INPUT: &str = r#"{ @@ -42,47 +49,25 @@ static mut TARGET_B_OUTPUT: Vec = vec![]; #[test] fn test_mod_b_export() { let expected_result = r#"{"name":"new name: gid://shopify/Order/1234567890","country":"CA"}"#; - mod_b::export(); + some_function_export(); let actual_result = std::str::from_utf8(unsafe { TARGET_B_OUTPUT.as_slice() }).unwrap(); assert_eq!(actual_result, expected_result); } -#[shopify_function_target( - target = "test.target-b", - module_name = "mod_b", - query_path = "./tests/fixtures/b.graphql", - schema_path = "./tests/fixtures/schema_with_targets.graphql", +#[shopify_function( input_stream = std::io::Cursor::new(TARGET_B_INPUT.as_bytes().to_vec()), output_stream = unsafe { &mut TARGET_B_OUTPUT }, )] fn some_function( - input: mod_b::input::ResponseData, -) -> Result { - Ok(mod_b::output::FunctionTargetBResult { + input: schema_with_targets::target_a::Input, +) -> Result { + Ok(schema_with_targets::FunctionTargetBResult { name: Some(format!("new name: {}", input.id)), country: Some("CA".to_string()), }) } -// Verify that the CountryCode enum is generated when `extern_enums = []` -#[shopify_function_target( - target = "test.target-a", - module_name = "country_enum", - query_path = "./tests/fixtures/input.graphql", - schema_path = "./tests/fixtures/schema_with_targets.graphql", - extern_enums = [] -)] -fn _with_generated_country_code( - input: country_enum::input::ResponseData, -) -> Result { - use country_enum::*; +#[typegen("./tests/fixtures/schema_with_targets.graphql", enums_as_str = [])] +mod schema_with_country_enum {} - let status = match input.country { - Some(input::CountryCode::CA) => 200, - _ => 201, - }; - - Ok(output::FunctionTargetAResult { - status: Some(status), - }) -} +const _: schema_with_country_enum::CountryCode = schema_with_country_enum::CountryCode::Ca; diff --git a/shopify_function_macro/Cargo.toml b/shopify_function_macro/Cargo.toml index 951e2ad..36aac09 100644 --- a/shopify_function_macro/Cargo.toml +++ b/shopify_function_macro/Cargo.toml @@ -10,8 +10,12 @@ description = "Macros for the `shopify_function` crate." proc-macro = true [dependencies] -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } quote = "1.0" proc-macro2 = "1.0.92" -convert_case = "0.6.0" -graphql_client_codegen = "0.14.0" +bluejay-typegen-codegen = { git = "https://github.com/Shopify/bluejay", rev = "fc9a7417ab0275151f5e3fa38d78839d5222618e", default-features = false } + +[features] +default = ["serde"] +serde = ["bluejay-typegen-codegen/serde"] +miniserde = ["bluejay-typegen-codegen/miniserde"] diff --git a/shopify_function_macro/src/lib.rs b/shopify_function_macro/src/lib.rs index d3e34e1..f3a8278 100644 --- a/shopify_function_macro/src/lib.rs +++ b/shopify_function_macro/src/lib.rs @@ -1,15 +1,12 @@ -use convert_case::{Case, Casing}; -use graphql_client_codegen::{ - generate_module_token_stream_from_string, CodegenMode, GraphQLClientCodegenOptions, -}; -use std::path::Path; +use std::collections::HashMap; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; +use bluejay_typegen_codegen::{generate_schema, Input as BluejayInput, KnownCustomScalarType}; +use proc_macro2::Span; +use quote::{format_ident, quote, ToTokens}; use syn::{ self, parse::{Parse, ParseStream}, - parse_macro_input, Expr, ExprArray, FnArg, LitStr, Token, + parse_macro_input, Expr, FnArg, Token, }; #[derive(Clone, Default)] @@ -19,120 +16,6 @@ struct ShopifyFunctionArgs { } impl ShopifyFunctionArgs { - fn parse_expression(input: &ParseStream<'_>) -> syn::Result { - input.parse::()?; - input.parse::()?; - let value: Expr = input.parse()?; - Ok(value) - } -} - -impl Parse for ShopifyFunctionArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut args = Self::default(); - while !input.is_empty() { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::input_stream) { - args.input_stream = Some(Self::parse_expression::(&input)?); - } else if lookahead.peek(kw::output_stream) { - args.output_stream = Some(Self::parse_expression::(&input)?); - } else { - // Ignore unknown tokens - let _ = input.parse::(); - } - } - Ok(args) - } -} - -/// Marks a function as a Shopify Function entry point. -/// -/// This attribute marks the following function as the entry point -/// for a Shopify Function. A Shopify Function takes exactly one -/// parameter of type `input::ResponseData`, and returns a -/// `Result`. Both of these types are generated -/// at build time from the Shopify's GraphQL schema. Take a look at the -/// [`macro@generate_types`] macro for details on those types. -/// -/// ```ignore -/// #[shopify_function] -/// fn function(input: input::ResponseData) -> Result { -/// /* ... */ -/// } -/// ``` -/// -/// By default, the function input is read from stdin and the result -/// is outputted to stdout. To override this, optional `input_stream` -/// and `output_stream` parameters can be set. These parameters must -/// implement the std::io::Read and std::io::Write traits respectively. -/// -/// ```ignore -/// #[shopify_function(input_stream = MyInputStream, output_stream = MyOutputStream)] -/// fn function(input: input::ResponseData) -> Result { -/// /* ... */ -/// } -/// ``` -#[proc_macro_attribute] -pub fn shopify_function( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let ast = parse_macro_input!(item as syn::ItemFn); - let args = parse_macro_input!(attr as ShopifyFunctionArgs); - - let name = &ast.sig.ident; - if ast.sig.inputs.len() != 1 { - return quote! {compile_error!("Shopify functions need exactly one input parameter");} - .into(); - } - - let input_type = match &ast.sig.inputs.first().unwrap() { - FnArg::Typed(input) => input.ty.as_ref(), - FnArg::Receiver(_) => { - return quote! {compile_error!("Shopify functions can’t have a receiver");}.into() - } - }; - - let input_stream = args - .input_stream - .map_or(quote! { std::io::stdin() }, |stream| { - stream.to_token_stream() - }); - let output_stream = args - .output_stream - .map_or(quote! { std::io::stdout() }, |stream| { - stream.to_token_stream() - }); - - let gen = quote! { - fn main() -> ::shopify_function::Result<()> { - let mut string = String::new(); - std::io::Read::read_to_string(&mut #input_stream, &mut string)?; - let input: #input_type = serde_json::from_str(&string)?; - let mut out = #output_stream; - let result = #name(input)?; - let serialized = serde_json::to_vec(&result)?; - std::io::Write::write_all(&mut out, serialized.as_slice())?; - Ok(()) - } - #ast - }; - - gen.into() -} - -#[derive(Clone, Default)] -struct ShopifyFunctionTargetArgs { - target: Option, - module_name: Option, - query_path: Option, - schema_path: Option, - input_stream: Option, - output_stream: Option, - extern_enums: Option, -} - -impl ShopifyFunctionTargetArgs { fn parse( input: &ParseStream<'_>, ) -> syn::Result { @@ -146,71 +29,15 @@ impl ShopifyFunctionTargetArgs { } } -impl Parse for ShopifyFunctionTargetArgs { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut args = Self::default(); - while !input.is_empty() { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::target) { - args.target = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::module_name) { - args.module_name = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::query_path) { - args.query_path = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::schema_path) { - args.schema_path = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::input_stream) { - args.input_stream = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::output_stream) { - args.output_stream = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::extern_enums) { - args.extern_enums = Some(Self::parse::(&input)?); - } else { - return Err(lookahead.error()); - } - } - Ok(args) - } -} - -#[derive(Clone, Default)] -struct GenerateTypeArgs { - query_path: Option, - schema_path: Option, - input_stream: Option, - output_stream: Option, - extern_enums: Option, -} - -impl GenerateTypeArgs { - fn parse( - input: &ParseStream<'_>, - ) -> syn::Result { - input.parse::()?; - input.parse::()?; - let value: V = input.parse()?; - if input.lookahead1().peek(Token![,]) { - input.parse::()?; - } - Ok(value) - } -} - -impl Parse for GenerateTypeArgs { +impl Parse for ShopifyFunctionArgs { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut args = Self::default(); while !input.is_empty() { let lookahead = input.lookahead1(); - if lookahead.peek(kw::query_path) { - args.query_path = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::schema_path) { - args.schema_path = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::input_stream) { + if lookahead.peek(kw::input_stream) { args.input_stream = Some(Self::parse::(&input)?); } else if lookahead.peek(kw::output_stream) { args.output_stream = Some(Self::parse::(&input)?); - } else if lookahead.peek(kw::extern_enums) { - args.extern_enums = Some(Self::parse::(&input)?); } else { return Err(lookahead.error()); } @@ -273,79 +100,31 @@ fn extract_shopify_function_return_type(ast: &syn::ItemFn) -> Result<&syn::Ident /// - Generate types based on the GraphQL schema for the Function input and output. /// - Define a wrapper function that's exported to Wasm. The wrapper handles /// decoding the input from STDIN, and encoding the output to STDOUT. -/// -/// -/// The macro takes the following parameters: -/// - `query_path`: A path to a GraphQL query, whose result will be used -/// as the input for the function invocation. The query MUST be named "Input". -/// - `schema_path`: A path to Shopify's GraphQL schema definition. Use the CLI -/// to download a fresh copy. -/// - `target` (optional): The API-specific handle for the target if the function name does not match the target handle as `snake_case` -/// - `module_name` (optional): The name of the generated module. -/// - default: The target handle as `snake_case` -/// - `extern_enums` (optional): A list of Enums for which an external type should be used. -/// For those, code generation will be skipped. This is useful for large enums -/// which can increase binary size, or for enums shared between multiple targets. -/// Example: `extern_enums = ["LanguageCode"]` -/// - default: `["LanguageCode", "CountryCode", "CurrencyCode"]` +/// - If the `codec` argument is provided, it will use the specified codec for +/// serialization and deserialization. #[proc_macro_attribute] -pub fn shopify_function_target( +pub fn shopify_function( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let ast = parse_macro_input!(item as syn::ItemFn); - let args = parse_macro_input!(attr as ShopifyFunctionTargetArgs); + let args = parse_macro_input!(attr as ShopifyFunctionArgs); let function_name = &ast.sig.ident; let function_name_string = function_name.to_string(); - let target_handle_string = args.target.map_or(function_name_string.clone(), |target| { - target - .value() - .split('.') - .collect::>() - .last() - .unwrap() - .to_case(Case::Snake) - }); - let module_name = args.module_name.map_or( - Ident::new(&target_handle_string, Span::mixed_site()), - |module_name| Ident::new(module_name.value().as_str(), Span::mixed_site()), - ); - - let query_path = args - .query_path - .expect("No value given for query_path") - .value(); - let schema_path = args - .schema_path - .expect("No value given for schema_path") - .value(); - let extern_enums = args - .extern_enums - .as_ref() - .map(extract_extern_enums) - .unwrap_or_else(default_exter_enums); - - let input_struct = generate_input_struct( - query_path.as_str(), - schema_path.as_str(), - extern_enums.as_slice(), - ); + let export_function_name = format_ident!("{}_export", function_name); - if let Err(error) = extract_shopify_function_return_type(&ast) { - return error.to_compile_error().into(); + if ast.sig.inputs.len() != 1 { + return quote! {compile_error!("Shopify functions need exactly one input parameter");} + .into(); } - let output_result_type = extract_shopify_function_return_type(&ast) - .unwrap() - .to_token_stream() - .to_string(); - let output_query = format!( - "mutation Output($result: {}!) {{\n {}(result: $result)\n}}\n", - output_result_type, - &target_handle_string.to_case(Case::Camel) - ); - let output_struct = - generate_output_struct(&output_query, schema_path.as_str(), extern_enums.as_slice()); + + let input_type = match &ast.sig.inputs.first().unwrap() { + FnArg::Typed(input) => input.ty.as_ref(), + FnArg::Receiver(_) => { + return quote! {compile_error!("Shopify functions can’t have a receiver");}.into() + } + }; if let Err(error) = extract_shopify_function_return_type(&ast) { return error.to_compile_error().into(); @@ -362,171 +141,195 @@ pub fn shopify_function_target( stream.to_token_stream() }); - quote! { - pub mod #module_name { - use super::*; - use std::io::Write; - - #input_struct - #output_struct - - #[shopify_function( - input_stream = #input_stream, - output_stream = #output_stream - )] - pub #ast - - #[export_name = #function_name_string] - pub extern "C" fn export() { - main().unwrap(); - #output_stream.flush().unwrap(); - } + let deserialize_line: syn::Stmt = if cfg!(feature = "serde") { + syn::parse_quote! { + let input: #input_type = shopify_function::serde_json::from_str(&string).unwrap(); } - pub use #module_name::#function_name; - } - .into() -} - -/// Generate the types to interact with Shopify's API. -/// -/// The macro generates two inline modules: `input` and `output`. The -/// modules generate Rust types from the GraphQL schema file for the Function input -/// and output respectively. -/// -/// The macro takes the following parameters: -/// - `query_path`: A path to a GraphQL query, whose result will be used -/// as the input for the function invocation. The query MUST be named "Input". -/// - `schema_path`: A path to Shopify's GraphQL schema definition. Use the CLI -/// to download a fresh copy. -/// - `extern_enums` (optional): A list of Enums for which an external type should be used. -/// For those, code generation will be skipped. This is useful for large enums -/// which can increase binary size, or for enums shared between multiple targets. -/// Example: `extern_enums = ["LanguageCode"]` -/// - default: `["LanguageCode", "CountryCode", "CurrencyCode"]` -#[proc_macro] -pub fn generate_types(attr: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args = parse_macro_input!(attr as GenerateTypeArgs); + } else { + syn::parse_quote! { + let input: #input_type = shopify_function::miniserde::json::from_str(&string).unwrap(); + } + }; - let query_path = args - .query_path - .expect("No value given for query_path") - .value(); - let schema_path = args - .schema_path - .expect("No value given for schema_path") - .value(); - let extern_enums = args - .extern_enums - .as_ref() - .map(extract_extern_enums) - .unwrap_or_else(default_exter_enums); + let serialize_line: syn::Stmt = if cfg!(feature = "serde") { + syn::parse_quote! { + let serialized = shopify_function::serde_json::to_vec(&result).unwrap(); + } + } else { + syn::parse_quote! { + let serialized = shopify_function::miniserde::json::to_string(&result); + } + }; - let input_struct = generate_input_struct( - query_path.as_str(), - schema_path.as_str(), - extern_enums.as_slice(), - ); - let output_query = - "mutation Output($result: FunctionResult!) {\n handleResult(result: $result)\n}\n"; - let output_struct = generate_output_struct(output_query, &schema_path, extern_enums.as_slice()); + let write_line: syn::Stmt = if cfg!(feature = "serde") { + syn::parse_quote! { + std::io::Write::write_all(&mut out, serialized.as_slice()).unwrap(); + } + } else { + syn::parse_quote! { + std::io::Write::write_all(&mut out, serialized.as_bytes()).unwrap(); + } + }; quote! { - #input_struct - #output_struct + #[export_name = #function_name_string] + pub extern "C" fn #export_function_name() { + let mut string = String::new(); + std::io::Read::read_to_string(&mut #input_stream, &mut string).unwrap(); + #deserialize_line + let mut out = #output_stream; + let result = #function_name(input).unwrap(); + #serialize_line + #write_line + std::io::Write::flush(&mut out).unwrap(); + } + + #ast } .into() } const DEFAULT_EXTERN_ENUMS: &[&str] = &["LanguageCode", "CountryCode", "CurrencyCode"]; -fn generate_input_struct( - query_path: &str, - schema_path: &str, - extern_enums: &[String], -) -> TokenStream { - quote! { - #[derive(graphql_client::GraphQLQuery, Clone, Debug, serde::Deserialize, PartialEq)] - #[graphql( - query_path = #query_path, - schema_path = #schema_path, - response_derives = "Clone,Debug,PartialEq,Deserialize,Serialize", - variables_derives = "Clone,Debug,PartialEq,Deserialize", - extern_enums(#(#extern_enums),*), - skip_serializing_none - )] - pub struct Input; - } -} - -fn graphql_codegen_options( - operation_name: String, - extern_enums: &[String], -) -> GraphQLClientCodegenOptions { - let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Derive); - options.set_operation_name(operation_name); - options.set_response_derives("Clone,Debug,PartialEq,Deserialize,Serialize".to_string()); - options.set_variables_derives("Clone,Debug,PartialEq,Deserialize".to_string()); - options.set_skip_serializing_none(true); - options.set_module_visibility( - syn::VisPublic { - pub_token: ::default(), - } - .into(), - ); - options.set_extern_enums(extern_enums.to_vec()); - - options +mod kw { + syn::custom_keyword!(input_stream); + syn::custom_keyword!(output_stream); } -fn generate_output_struct( - query: &str, - schema_path: &str, - extern_enums: &[String], -) -> proc_macro2::TokenStream { - let options = graphql_codegen_options("Output".to_string(), extern_enums); - let cargo_manifest_dir = - std::env::var("CARGO_MANIFEST_DIR").expect("Error reading CARGO_MANIFEST_DIR from env"); - let schema_path = Path::new(&cargo_manifest_dir).join(schema_path); - let token_stream = generate_module_token_stream_from_string(query, &schema_path, options) - .expect("Error generating Output struct"); - - quote! { - #token_stream - pub struct Output; +// mostly copied from bluejay_typegen_codegen +/// Generates Rust types from GraphQL schema definitions and queries. +/// +/// ### Arguments +/// +/// **Positional:** +/// +/// 1. String literal with path to the file containing the schema definition. If relative, should be with respect to +/// the project root (wherever `Cargo.toml` is located). +/// +/// **Optional keyword:** +/// +/// _borrow_: Boolean literal indicating whether the generated types should borrow where possible. Defaults to `false`. +/// When `true`, deserializing must be done from a string as a opposed to `serde_json::Value` or a reader. +/// +/// _codec_: String literal indicating the codec to use for serialization and deserialization. Defaults to `serde`. +/// Allowed values are `serde` and `miniserde`. `miniserde` is only available when the `miniserde` feature is enabled. +/// +/// _enums_as_str_: Optional list of enum names for which the generated code should use string types instead of +/// a fully formed enum. Defaults to `["LanguageCode", "CountryCode", "CurrencyCode"]`. +/// +/// ### Trait implementations +/// +/// By default, will implement `PartialEq`, `Eq`, `Clone`, and `Debug` for all types. Will implement `Copy` for enums. +/// For types corresponding to values returned from queries, the relevant deserialization trait for the selected codec +/// is implemented (e.g. `serde::Deserialize` in the case of `serde`). For types that would +/// be arguments to a query, the relevant serialization trait for the selected codec is implemented +/// (e.g. `serde::Serialize` for the `serde` codec). +/// +/// ### Usage +/// +/// Must be used with a module. Inside the module, type aliases must be defined for any custom scalars in the schema. +/// To use a query, define a module within the aforementioned module, and annotate it with +/// `#[query("path/to/query.graphql")]`, where the argument is a string literal path to the query document, or the +/// query contents enclosed in square brackets. +/// +/// ### Naming +/// +/// To generate idiomatic Rust code, some renaming of types, enum variants, and fields is performed. Types are +/// renamed with `PascalCase`, as are enum variants. Fields are renamed with `snake_case`. +/// +/// ### Query restrictions +/// +/// In order to keep the type generation code relatively simple, there are some restrictions on the queries that are +/// permitted. This may be relaxed in future versions. +/// * Selection sets on object and interface types must contain either a single fragment spread, or entirely field +/// selections. +/// * Selection sets on union types must contain either a single fragment spread, or both an unaliased `__typename` +/// selection and inline fragments for all or a subset of the objects contained in the union. +#[proc_macro_attribute] +pub fn typegen( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let mut input = syn::parse_macro_input!(attr as BluejayInput); + if cfg!(feature = "serde") { + input.serde_path = Some(syn::parse_quote! { ::shopify_function::serde }); + } else { + input.serde_path = None; } -} + if cfg!(feature = "miniserde") { + input.miniserde_path = Some(syn::parse_quote! { ::shopify_function::miniserde }); + } else { + input.miniserde_path = None; + } + let mut module = syn::parse_macro_input!(item as syn::ItemMod); -fn extract_extern_enums(extern_enums: &ExprArray) -> Vec { - let extern_enum_error_msg = r#"The `extern_enums` attribute expects comma separated string literals\n\n= help: use `extern_enums = ["Enum1", "Enum2"]`"#; - extern_enums - .elems - .iter() - .map(|expr| { - let value = match expr { - Expr::Lit(lit) => lit.lit.clone(), - _ => panic!("{}", extern_enum_error_msg), - }; - match value { - syn::Lit::Str(lit) => lit.value(), - _ => panic!("{}", extern_enum_error_msg), - } - }) - .collect() -} + let string_known_custom_scalar_type = KnownCustomScalarType { + path_for_borrowed: Some(syn::parse_quote! { ::std::borrow::Cow<'a, str> }), + path_for_owned: syn::parse_quote! { ::std::string::String }, + }; -fn default_exter_enums() -> Vec { - DEFAULT_EXTERN_ENUMS.iter().map(|e| e.to_string()).collect() -} + let known_custom_scalar_types = HashMap::from([ + (String::from("Id"), string_known_custom_scalar_type.clone()), + (String::from("Url"), string_known_custom_scalar_type.clone()), + ( + String::from("Handle"), + string_known_custom_scalar_type.clone(), + ), + ( + String::from("Date"), + string_known_custom_scalar_type.clone(), + ), + ( + String::from("DateTime"), + string_known_custom_scalar_type.clone(), + ), + ( + String::from("DateTimeWithoutTimezone"), + string_known_custom_scalar_type.clone(), + ), + ( + String::from("TimeWithoutTimezone"), + string_known_custom_scalar_type.clone(), + ), + ( + String::from("Void"), + KnownCustomScalarType { + path_for_borrowed: None, + path_for_owned: syn::parse_quote! { () }, + }, + ), + ( + String::from("Json"), + KnownCustomScalarType { + path_for_borrowed: None, + path_for_owned: if cfg!(feature = "serde") { + syn::parse_quote! { ::shopify_function::serde_json::Value } + } else { + syn::parse_quote! { ::shopify_function::miniserde::json::Value } + }, + }, + ), + ( + String::from("Decimal"), + KnownCustomScalarType { + path_for_borrowed: None, + path_for_owned: syn::parse_quote! { ::shopify_function::scalars::Decimal }, + }, + ), + ]); + + if input.enums_as_str.is_none() { + let enums_as_str = DEFAULT_EXTERN_ENUMS + .iter() + .map(|enum_name| syn::LitStr::new(enum_name, Span::mixed_site())) + .collect::>(); + input.enums_as_str = Some(syn::parse_quote! { #(#enums_as_str),* }); + input.allow_unknown_enums_as_str = true; + } -#[cfg(test)] -mod tests {} + if let Err(error) = generate_schema(input, &mut module, known_custom_scalar_types) { + return error.to_compile_error().into(); + } -mod kw { - syn::custom_keyword!(target); - syn::custom_keyword!(module_name); - syn::custom_keyword!(query_path); - syn::custom_keyword!(schema_path); - syn::custom_keyword!(input_stream); - syn::custom_keyword!(output_stream); - syn::custom_keyword!(extern_enums); + module.to_token_stream().into() }