From aebb492e4a8718a6bfe429bd9989735843eec38e Mon Sep 17 00:00:00 2001 From: Martin Gingras Date: Wed, 7 May 2025 10:48:05 -0400 Subject: [PATCH] Update Validation function templates --- .../default/schema.graphql | 94 +++++++++++++- .../default/shopify.extension.toml.liquid | 9 +- ...t_validations_generate_run.graphql.liquid} | 2 +- .../src/cart_validations_generate_run.liquid | 56 +++++++++ .../cart_validations_generate_run.test.liquid | 119 ++++++++++++++++++ .../default/src/index.liquid | 2 +- .../default/src/run.liquid | 44 ------- .../default/src/run.test.liquid | 86 ------------- .../default/schema.graphql | 94 +++++++++++++- .../default/shopify.extension.toml.liquid | 9 +- ...t_validations_generate_run.graphql.liquid} | 0 ...un.rs => cart_validations_generate_run.rs} | 41 +++--- .../default/src/main.rs | 2 +- ...t_validations_generate_run.graphql.liquid} | 0 .../default/schema.graphql | 94 +++++++++++++- .../default/shopify.extension.toml.liquid | 9 +- 16 files changed, 480 insertions(+), 181 deletions(-) rename checkout/javascript/cart-checkout-validation/default/src/{run.graphql.liquid => cart_validations_generate_run.graphql.liquid} (54%) create mode 100644 checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.liquid create mode 100644 checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.test.liquid delete mode 100644 checkout/javascript/cart-checkout-validation/default/src/run.liquid delete mode 100644 checkout/javascript/cart-checkout-validation/default/src/run.test.liquid rename checkout/rust/cart-checkout-validation/default/src/{run.graphql.liquid => cart_validations_generate_run.graphql.liquid} (100%) rename checkout/rust/cart-checkout-validation/default/src/{run.rs => cart_validations_generate_run.rs} (53%) rename checkout/wasm/cart-checkout-validation/default/{run.graphql.liquid => cart_validations_generate_run.graphql.liquid} (100%) diff --git a/checkout/javascript/cart-checkout-validation/default/schema.graphql b/checkout/javascript/cart-checkout-validation/default/schema.graphql index b96445f5..27148a73 100644 --- a/checkout/javascript/cart-checkout-validation/default/schema.graphql +++ b/checkout/javascript/cart-checkout-validation/default/schema.graphql @@ -13,6 +13,11 @@ Scale the Functions resource limits based on the field's length. """ directive @scaleLimits(rate: Float!) on FIELD_DEFINITION +""" +Requires that exactly one field must be supplied and that field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + """ A custom property. Attributes are used to store additional information about a Shopify resource, such as products, customers, or orders. Attributes are stored as key-value pairs. @@ -352,6 +357,27 @@ type CartLineCost { totalAmount: MoneyV2! } +""" +The fetch target result. Your Function must return this data structure when generating the request. +""" +input CartValidationsGenerateFetchResult { + """ + The attributes associated with an HTTP request. + """ + request: HttpRequest +} + +""" +The output of the Function run target. The object contains the validation errors +that display to customers and prevent them from proceeding through checkout. +""" +input CartValidationsGenerateRunResult { + """ + The ordered list of operations to apply. + """ + operations: [Operation!]! +} + """ Whether the product is in the specified collection. @@ -3012,9 +3038,9 @@ type HttpResponse { headers: [HttpResponseHeader!]! @deprecated(reason: "Use `header` instead.") """ - The HTTP response body parsed as JSON. - If the body is valid JSON, it will be parsed and returned as a JSON object. - If parsing fails, then raw body is returned as a string. + The HTTP response body parsed as JSON. + If the body is valid JSON, it will be parsed and returned as a JSON object. + If parsing fails, then raw body is returned as a string. Use this field when you expect the response to be JSON, or when you're dealing with mixed response types, meaning both JSON and non-JSON. Using this field reduces function instruction consumption and ensures that the data is formatted in logs. @@ -3081,7 +3107,7 @@ type Input { your fetch target, and that is passed as input to the run target. For more information, refer to [network access for Shopify Functions](https://shopify.dev/docs/apps/build/functions/input-output/network-access). """ - fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run"]) + fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run", "cart.validations.generate.run"]) """ The regional and language settings that determine how the Function @@ -3955,7 +3981,7 @@ type Localization { """ The market of the active localized experience. """ - market: Market! + market: Market! @deprecated(reason: "This `market` field will be removed in a future version of the API.") } """ @@ -4223,7 +4249,7 @@ type MailingAddress { """ The market of the address. """ - market: Market + market: Market @deprecated(reason: "This `market` field will be removed in a future version of the API.") """ The full name of the customer, based on firstName and lastName. @@ -4382,6 +4408,26 @@ type MoneyV2 { The root mutation for the API. """ type MutationRoot { + """ + Handles the Function result for the cart.validations.generate.fetch target. + """ + cartValidationsGenerateFetch( + """ + The result of the Function. + """ + result: CartValidationsGenerateFetchResult! + ): Void! + + """ + Handles the Function result for the cart.validations.generate.run target. + """ + cartValidationsGenerateRun( + """ + The result of the Function. + """ + result: CartValidationsGenerateRunResult! + ): Void! + """ Handles the Function result for the purchase.validation.fetch target. """ @@ -4413,6 +4459,16 @@ type MutationRoot { ): Void! } +""" +An operation to apply. +""" +input Operation @oneOf { + """ + Add a performed validation. + """ + validationAdd: ValidationAddOperation +} + """ The goods and services that merchants offer to customers. Products can include details such as title, vendor, and custom data stored in [metafields](https://shopify.dev/docs/apps/build/custom-data). @@ -4802,6 +4858,32 @@ type Validation implements HasMetafields { ): Metafield } +""" +Add a performed validation. +""" +input ValidationAddOperation { + """ + Errors. + """ + errors: [ValidationError!]! +} + +""" +A Function error for a path. +""" +input ValidationError { + """ + Returns a message describing the error. + """ + message: String! + + """ + Specifies the path/target for use by the UI. See [Supported checkout field targets](https://shopify.dev/docs/api/functions/reference/cart-checkout-validation/graphql#supported-checkout-field-targets) + for a list of supported targets. + """ + target: String! +} + """ A void type that can be used to return a null value from a mutation. """ diff --git a/checkout/javascript/cart-checkout-validation/default/shopify.extension.toml.liquid b/checkout/javascript/cart-checkout-validation/default/shopify.extension.toml.liquid index 38cff493..abbf39c4 100644 --- a/checkout/javascript/cart-checkout-validation/default/shopify.extension.toml.liquid +++ b/checkout/javascript/cart-checkout-validation/default/shopify.extension.toml.liquid @@ -1,4 +1,4 @@ -api_version = "2025-01" +api_version = "2025-07" [[extensions]] name = "t:name" @@ -8,11 +8,10 @@ type = "function" description = "t:description" [[extensions.targeting]] - target = "purchase.validation.run" - input_query = "src/run.graphql" - export = "run" + target = "cart.validations.generate.run" + input_query = "src/cart_validations_generate_run.graphql" + export = "cart-validations-generate-run" [extensions.build] command = "" path = "dist/function.wasm" - diff --git a/checkout/javascript/cart-checkout-validation/default/src/run.graphql.liquid b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.graphql.liquid similarity index 54% rename from checkout/javascript/cart-checkout-validation/default/src/run.graphql.liquid rename to checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.graphql.liquid index 82abd594..475dc446 100644 --- a/checkout/javascript/cart-checkout-validation/default/src/run.graphql.liquid +++ b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.graphql.liquid @@ -1,4 +1,4 @@ -query RunInput { +query CartValidationsGenerateRunInput { cart { lines { quantity diff --git a/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.liquid b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.liquid new file mode 100644 index 00000000..1cf13bd2 --- /dev/null +++ b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.liquid @@ -0,0 +1,56 @@ +{%- if flavor contains "vanilla-js" -%} +// @ts-check + +/** + * @typedef {import("../generated/api").CartValidationsGenerateRunInput} CartValidationsGenerateRunInput + * @typedef {import("../generated/api").CartValidationsGenerateRunResult} CartValidationsGenerateRunResult + */ + +/** + * @param {CartValidationsGenerateRunInput} input + * @returns {CartValidationsGenerateRunResult} + */ +export function cartValidationsGenerateRun(input) { + const errors = input.cart.lines + .filter(({ quantity }) => quantity > 1) + .map(() => ({ + message: "Not possible to order more than one of each", + target: "$.cart" + })); + + const operations = [ + { + validationAdd: { + errors + }, + }, + ]; + + return { operations }; +}; +{%- elsif flavor contains "typescript" -%} +import type { + CartValidationsGenerateRunInput, + CartValidationsGenerateRunResult, + ValidationError, +} from "../generated/api"; + +export function cartValidationsGenerateRun(input: CartValidationsGenerateRunInput): CartValidationsGenerateRunResult { + const errors: ValidationError[] = input.cart.lines + .filter(({ quantity }) => quantity > 1) + .map(() => ({ + message: "Not possible to order more than one of each", + target: "$.cart" + })); + + const operations = [ + { + validationAdd: { + errors + }, + }, + ]; + + return { operations }; +}; +{%- endif -%} diff --git a/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.test.liquid b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.test.liquid new file mode 100644 index 00000000..b343d706 --- /dev/null +++ b/checkout/javascript/cart-checkout-validation/default/src/cart_validations_generate_run.test.liquid @@ -0,0 +1,119 @@ +{%- if flavor contains "vanilla-js" -%} +import { describe, it, expect } from 'vitest'; +import { cartValidationsGenerateRun } from './cart_validations_generate_run'; + +/** + * @typedef {import("../generated/api").CartValidationsGenerateRunResult} CartValidationsGenerateRunResult + */ + +describe('cart checkout validation function', () => { + it('returns an error when quantity exceeds one', () => { + const result = cartValidationsGenerateRun({ + cart: { + lines: [ + { + quantity: 3 + } + ] + } + }); + const expected = /** @type {CartValidationsGenerateRunResult} */ ({ + operations: [ + { + validationAdd: { + errors: [ + { + message: "Not possible to order more than one of each", + target: "$.cart" + } + ] + } + } + ] + }); + + expect(result).toEqual(expected); + }); + + it('returns no errors when quantity is one', () => { + const result = cartValidationsGenerateRun({ + cart: { + lines: [ + { + quantity: 1 + } + ] + } + }); + const expected = /** @type {CartValidationsGenerateRunResult} */ ({ + operations: [ + { + validationAdd: { + errors: [] + } + } + ] + }); + + expect(result).toEqual(expected); + }); +}); + +{%- elsif flavor contains "typescript" -%} +import { describe, it, expect } from 'vitest'; +import { cartValidationsGenerateRun } from './cart_validations_generate_run'; +import { CartValidationsGenerateRunResult } from "../generated/api"; + +describe('cart checkout validation function', () => { + it('returns an error when quantity exceeds one', () => { + const result = cartValidationsGenerateRun({ + cart: { + lines: [ + { + quantity: 3 + } + ] + } + }); + const expected: CartValidationsGenerateRunResult = { + operations: [ + { + validationAdd: { + errors: [ + { + message: "Not possible to order more than one of each", + target: "$.cart" + } + ] + } + } + ] + }; + + expect(result).toEqual(expected); + }); + + it('returns no errors when quantity is one', () => { + const result = cartValidationsGenerateRun({ + cart: { + lines: [ + { + quantity: 1 + } + ] + } + }); + const expected: CartValidationsGenerateRunResult = { + operations: [ + { + validationAdd: { + errors: [] + } + } + ] + }; + + expect(result).toEqual(expected); + }); +}); +{%- endif -%} diff --git a/checkout/javascript/cart-checkout-validation/default/src/index.liquid b/checkout/javascript/cart-checkout-validation/default/src/index.liquid index 2e6f9676..77af359d 100644 --- a/checkout/javascript/cart-checkout-validation/default/src/index.liquid +++ b/checkout/javascript/cart-checkout-validation/default/src/index.liquid @@ -1 +1 @@ -export * from './run'; +export * from './cart_validations_generate_run'; diff --git a/checkout/javascript/cart-checkout-validation/default/src/run.liquid b/checkout/javascript/cart-checkout-validation/default/src/run.liquid deleted file mode 100644 index 4c76a0a9..00000000 --- a/checkout/javascript/cart-checkout-validation/default/src/run.liquid +++ /dev/null @@ -1,44 +0,0 @@ -{%- if flavor contains "vanilla-js" -%} -// @ts-check - -/** - * @typedef {import("../generated/api").RunInput} RunInput - * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult - */ - -/** - * @param {RunInput} input - * @returns {FunctionRunResult} - */ -export function run(input) { - const errors = input.cart.lines - .filter(({ quantity }) => quantity > 1) - .map(() => ({ - localizedMessage: "Not possible to order more than one of each", - target: "$.cart", - })); - - return { - errors - } -}; -{%- elsif flavor contains "typescript" -%} -import type { - RunInput, - FunctionRunResult, - FunctionError, -} from "../generated/api"; - -export function run(input: RunInput): FunctionRunResult { - const errors: FunctionError[] = input.cart.lines - .filter(({ quantity }) => quantity > 1) - .map(() => ({ - localizedMessage: "Not possible to order more than one of each", - target: "$.cart", - })); - - return { - errors - } -}; -{%- endif -%} diff --git a/checkout/javascript/cart-checkout-validation/default/src/run.test.liquid b/checkout/javascript/cart-checkout-validation/default/src/run.test.liquid deleted file mode 100644 index 47eefa63..00000000 --- a/checkout/javascript/cart-checkout-validation/default/src/run.test.liquid +++ /dev/null @@ -1,86 +0,0 @@ -{%- if flavor contains "vanilla-js" -%} -import { describe, it, expect } from 'vitest'; -import { run } from './run'; - -/** - * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult - */ - -describe('cart checkout validation function', () => { - it('returns an error when quantity exceeds one', () => { - const result = run({ - cart: { - lines: [ - { - quantity: 3 - } - ] - } - }); - const expected = /** @type {FunctionRunResult} */ ({ errors: [ - { - localizedMessage: "Not possible to order more than one of each", - target: "$.cart" - } - ] }); - - expect(result).toEqual(expected); - }); - - it('returns no errors when quantity is one', () => { - const result = run({ - cart: { - lines: [ - { - quantity: 1 - } - ] - } - }); - const expected = /** @type {FunctionRunResult} */ ({ errors: [] }); - - expect(result).toEqual(expected); - }); -}); -{%- elsif flavor contains "typescript" -%} -import { describe, it, expect } from 'vitest'; -import { run } from './run'; -import { FunctionRunResult } from "../generated/api"; - -describe('cart checkout validation function', () => { - it('returns an error when quantity exceeds one', () => { - const result = run({ - cart: { - lines: [ - { - quantity: 3 - } - ] - } - }); - const expected: FunctionRunResult = { errors: [ - { - localizedMessage: "Not possible to order more than one of each", - target: "$.cart" - } - ] }; - - expect(result).toEqual(expected); - }); - - it('returns no errors when quantity is one', () => { - const result = run({ - cart: { - lines: [ - { - quantity: 1 - } - ] - } - }); - const expected: FunctionRunResult = { errors: [] }; - - expect(result).toEqual(expected); - }); -}); -{%- endif -%} diff --git a/checkout/rust/cart-checkout-validation/default/schema.graphql b/checkout/rust/cart-checkout-validation/default/schema.graphql index b96445f5..27148a73 100644 --- a/checkout/rust/cart-checkout-validation/default/schema.graphql +++ b/checkout/rust/cart-checkout-validation/default/schema.graphql @@ -13,6 +13,11 @@ Scale the Functions resource limits based on the field's length. """ directive @scaleLimits(rate: Float!) on FIELD_DEFINITION +""" +Requires that exactly one field must be supplied and that field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + """ A custom property. Attributes are used to store additional information about a Shopify resource, such as products, customers, or orders. Attributes are stored as key-value pairs. @@ -352,6 +357,27 @@ type CartLineCost { totalAmount: MoneyV2! } +""" +The fetch target result. Your Function must return this data structure when generating the request. +""" +input CartValidationsGenerateFetchResult { + """ + The attributes associated with an HTTP request. + """ + request: HttpRequest +} + +""" +The output of the Function run target. The object contains the validation errors +that display to customers and prevent them from proceeding through checkout. +""" +input CartValidationsGenerateRunResult { + """ + The ordered list of operations to apply. + """ + operations: [Operation!]! +} + """ Whether the product is in the specified collection. @@ -3012,9 +3038,9 @@ type HttpResponse { headers: [HttpResponseHeader!]! @deprecated(reason: "Use `header` instead.") """ - The HTTP response body parsed as JSON. - If the body is valid JSON, it will be parsed and returned as a JSON object. - If parsing fails, then raw body is returned as a string. + The HTTP response body parsed as JSON. + If the body is valid JSON, it will be parsed and returned as a JSON object. + If parsing fails, then raw body is returned as a string. Use this field when you expect the response to be JSON, or when you're dealing with mixed response types, meaning both JSON and non-JSON. Using this field reduces function instruction consumption and ensures that the data is formatted in logs. @@ -3081,7 +3107,7 @@ type Input { your fetch target, and that is passed as input to the run target. For more information, refer to [network access for Shopify Functions](https://shopify.dev/docs/apps/build/functions/input-output/network-access). """ - fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run"]) + fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run", "cart.validations.generate.run"]) """ The regional and language settings that determine how the Function @@ -3955,7 +3981,7 @@ type Localization { """ The market of the active localized experience. """ - market: Market! + market: Market! @deprecated(reason: "This `market` field will be removed in a future version of the API.") } """ @@ -4223,7 +4249,7 @@ type MailingAddress { """ The market of the address. """ - market: Market + market: Market @deprecated(reason: "This `market` field will be removed in a future version of the API.") """ The full name of the customer, based on firstName and lastName. @@ -4382,6 +4408,26 @@ type MoneyV2 { The root mutation for the API. """ type MutationRoot { + """ + Handles the Function result for the cart.validations.generate.fetch target. + """ + cartValidationsGenerateFetch( + """ + The result of the Function. + """ + result: CartValidationsGenerateFetchResult! + ): Void! + + """ + Handles the Function result for the cart.validations.generate.run target. + """ + cartValidationsGenerateRun( + """ + The result of the Function. + """ + result: CartValidationsGenerateRunResult! + ): Void! + """ Handles the Function result for the purchase.validation.fetch target. """ @@ -4413,6 +4459,16 @@ type MutationRoot { ): Void! } +""" +An operation to apply. +""" +input Operation @oneOf { + """ + Add a performed validation. + """ + validationAdd: ValidationAddOperation +} + """ The goods and services that merchants offer to customers. Products can include details such as title, vendor, and custom data stored in [metafields](https://shopify.dev/docs/apps/build/custom-data). @@ -4802,6 +4858,32 @@ type Validation implements HasMetafields { ): Metafield } +""" +Add a performed validation. +""" +input ValidationAddOperation { + """ + Errors. + """ + errors: [ValidationError!]! +} + +""" +A Function error for a path. +""" +input ValidationError { + """ + Returns a message describing the error. + """ + message: String! + + """ + Specifies the path/target for use by the UI. See [Supported checkout field targets](https://shopify.dev/docs/api/functions/reference/cart-checkout-validation/graphql#supported-checkout-field-targets) + for a list of supported targets. + """ + target: String! +} + """ A void type that can be used to return a null value from a mutation. """ diff --git a/checkout/rust/cart-checkout-validation/default/shopify.extension.toml.liquid b/checkout/rust/cart-checkout-validation/default/shopify.extension.toml.liquid index 1851f0e5..3ac49d65 100644 --- a/checkout/rust/cart-checkout-validation/default/shopify.extension.toml.liquid +++ b/checkout/rust/cart-checkout-validation/default/shopify.extension.toml.liquid @@ -1,4 +1,4 @@ -api_version = "2025-01" +api_version = "2025-07" [[extensions]] name = "t:name" @@ -8,12 +8,11 @@ type = "function" description = "t:description" [[extensions.targeting]] - target = "purchase.validation.run" - input_query = "src/run.graphql" - export = "run" + target = "cart.validations.generate.run" + input_query = "src/cart_validations_generate_run.graphql" + export = "cart_validations_generate_run" [extensions.build] command = "cargo build --target=wasm32-wasip1 --release" path = "target/wasm32-wasip1/release/{{handle | replace: " ", "-" | downcase}}.wasm" watch = ["src/**/*.rs"] - diff --git a/checkout/rust/cart-checkout-validation/default/src/run.graphql.liquid b/checkout/rust/cart-checkout-validation/default/src/cart_validations_generate_run.graphql.liquid similarity index 100% rename from checkout/rust/cart-checkout-validation/default/src/run.graphql.liquid rename to checkout/rust/cart-checkout-validation/default/src/cart_validations_generate_run.graphql.liquid diff --git a/checkout/rust/cart-checkout-validation/default/src/run.rs b/checkout/rust/cart-checkout-validation/default/src/cart_validations_generate_run.rs similarity index 53% rename from checkout/rust/cart-checkout-validation/default/src/run.rs rename to checkout/rust/cart-checkout-validation/default/src/cart_validations_generate_run.rs index ca0fef5d..3d7fb727 100644 --- a/checkout/rust/cart-checkout-validation/default/src/run.rs +++ b/checkout/rust/cart-checkout-validation/default/src/cart_validations_generate_run.rs @@ -6,8 +6,9 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Default, PartialEq)] struct Config {} -#[shopify_function_target(query_path = "src/run.graphql", schema_path = "schema.graphql")] -fn run(input: input::ResponseData) -> Result { +#[shopify_function_target(query_path = "src/cart_validations_generate_run.graphql", schema_path = "schema.graphql")] +fn cart_validations_generate_run(input: input::ResponseData) -> Result { + let mut operations = Vec::new(); let mut errors = Vec::new(); if input @@ -17,12 +18,16 @@ fn run(input: input::ResponseData) -> Result { .map(|line| line.quantity) .any(|quantity| quantity > 1) { - errors.push(output::FunctionError { - localized_message: "Not possible to order more than one of each".to_owned(), + errors.push(output::ValidationError { + message: "Not possible to order more than one of each".to_owned(), target: "$.cart".to_owned(), }) } - Ok(output::FunctionRunResult { errors }) + + let operation = output::ValidationAddOperation { errors }; + operations.push(output::Operation::ValidationAdd(operation)); + + Ok(output::CartValidationsGenerateRunResult { operations }) } #[cfg(test)] @@ -32,10 +37,10 @@ mod tests { #[test] fn test_result_contains_single_error_when_quantity_exceeds_one() -> Result<()> { - use run::output::*; + use cart_validations_generate_run::output::*; let result = run_function_with_input( - run, + cart_validations_generate_run, r#" { "cart": { @@ -48,11 +53,13 @@ mod tests { } "#, )?; - let expected = FunctionRunResult { - errors: vec![FunctionError { - localized_message: "Not possible to order more than one of each".to_owned(), - target: "$.cart".to_owned(), - }], + let expected = CartValidationsGenerateRunResult { + operations: vec![Operation::ValidationAdd(ValidationAddOperation { + errors: vec![ValidationError { + message: "Not possible to order more than one of each".to_owned(), + target: "$.cart".to_owned(), + }], + })], }; assert_eq!(result, expected); @@ -61,10 +68,10 @@ mod tests { #[test] fn test_result_contains_no_errors_when_quantity_is_one() -> Result<()> { - use run::output::*; + use cart_validations_generate_run::output::*; let result = run_function_with_input( - run, + cart_validations_generate_run, r#" { "cart": { @@ -77,7 +84,11 @@ mod tests { } "#, )?; - let expected = FunctionRunResult { errors: vec![] }; + let expected = CartValidationsGenerateRunResult { + operations: vec![Operation::ValidationAdd(ValidationAddOperation { + errors: vec![], + })], + }; assert_eq!(result, expected); Ok(()) diff --git a/checkout/rust/cart-checkout-validation/default/src/main.rs b/checkout/rust/cart-checkout-validation/default/src/main.rs index 9272b027..a52c24dc 100644 --- a/checkout/rust/cart-checkout-validation/default/src/main.rs +++ b/checkout/rust/cart-checkout-validation/default/src/main.rs @@ -1,5 +1,5 @@ use std::process; -pub mod run; +pub mod cart_validations_generate_run; fn main() { eprintln!("Please invoke a named export."); diff --git a/checkout/wasm/cart-checkout-validation/default/run.graphql.liquid b/checkout/wasm/cart-checkout-validation/default/cart_validations_generate_run.graphql.liquid similarity index 100% rename from checkout/wasm/cart-checkout-validation/default/run.graphql.liquid rename to checkout/wasm/cart-checkout-validation/default/cart_validations_generate_run.graphql.liquid diff --git a/checkout/wasm/cart-checkout-validation/default/schema.graphql b/checkout/wasm/cart-checkout-validation/default/schema.graphql index b96445f5..27148a73 100644 --- a/checkout/wasm/cart-checkout-validation/default/schema.graphql +++ b/checkout/wasm/cart-checkout-validation/default/schema.graphql @@ -13,6 +13,11 @@ Scale the Functions resource limits based on the field's length. """ directive @scaleLimits(rate: Float!) on FIELD_DEFINITION +""" +Requires that exactly one field must be supplied and that field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + """ A custom property. Attributes are used to store additional information about a Shopify resource, such as products, customers, or orders. Attributes are stored as key-value pairs. @@ -352,6 +357,27 @@ type CartLineCost { totalAmount: MoneyV2! } +""" +The fetch target result. Your Function must return this data structure when generating the request. +""" +input CartValidationsGenerateFetchResult { + """ + The attributes associated with an HTTP request. + """ + request: HttpRequest +} + +""" +The output of the Function run target. The object contains the validation errors +that display to customers and prevent them from proceeding through checkout. +""" +input CartValidationsGenerateRunResult { + """ + The ordered list of operations to apply. + """ + operations: [Operation!]! +} + """ Whether the product is in the specified collection. @@ -3012,9 +3038,9 @@ type HttpResponse { headers: [HttpResponseHeader!]! @deprecated(reason: "Use `header` instead.") """ - The HTTP response body parsed as JSON. - If the body is valid JSON, it will be parsed and returned as a JSON object. - If parsing fails, then raw body is returned as a string. + The HTTP response body parsed as JSON. + If the body is valid JSON, it will be parsed and returned as a JSON object. + If parsing fails, then raw body is returned as a string. Use this field when you expect the response to be JSON, or when you're dealing with mixed response types, meaning both JSON and non-JSON. Using this field reduces function instruction consumption and ensures that the data is formatted in logs. @@ -3081,7 +3107,7 @@ type Input { your fetch target, and that is passed as input to the run target. For more information, refer to [network access for Shopify Functions](https://shopify.dev/docs/apps/build/functions/input-output/network-access). """ - fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run"]) + fetchResult: HttpResponse @restrictTarget(only: ["purchase.validation.run", "cart.validations.generate.run"]) """ The regional and language settings that determine how the Function @@ -3955,7 +3981,7 @@ type Localization { """ The market of the active localized experience. """ - market: Market! + market: Market! @deprecated(reason: "This `market` field will be removed in a future version of the API.") } """ @@ -4223,7 +4249,7 @@ type MailingAddress { """ The market of the address. """ - market: Market + market: Market @deprecated(reason: "This `market` field will be removed in a future version of the API.") """ The full name of the customer, based on firstName and lastName. @@ -4382,6 +4408,26 @@ type MoneyV2 { The root mutation for the API. """ type MutationRoot { + """ + Handles the Function result for the cart.validations.generate.fetch target. + """ + cartValidationsGenerateFetch( + """ + The result of the Function. + """ + result: CartValidationsGenerateFetchResult! + ): Void! + + """ + Handles the Function result for the cart.validations.generate.run target. + """ + cartValidationsGenerateRun( + """ + The result of the Function. + """ + result: CartValidationsGenerateRunResult! + ): Void! + """ Handles the Function result for the purchase.validation.fetch target. """ @@ -4413,6 +4459,16 @@ type MutationRoot { ): Void! } +""" +An operation to apply. +""" +input Operation @oneOf { + """ + Add a performed validation. + """ + validationAdd: ValidationAddOperation +} + """ The goods and services that merchants offer to customers. Products can include details such as title, vendor, and custom data stored in [metafields](https://shopify.dev/docs/apps/build/custom-data). @@ -4802,6 +4858,32 @@ type Validation implements HasMetafields { ): Metafield } +""" +Add a performed validation. +""" +input ValidationAddOperation { + """ + Errors. + """ + errors: [ValidationError!]! +} + +""" +A Function error for a path. +""" +input ValidationError { + """ + Returns a message describing the error. + """ + message: String! + + """ + Specifies the path/target for use by the UI. See [Supported checkout field targets](https://shopify.dev/docs/api/functions/reference/cart-checkout-validation/graphql#supported-checkout-field-targets) + for a list of supported targets. + """ + target: String! +} + """ A void type that can be used to return a null value from a mutation. """ diff --git a/checkout/wasm/cart-checkout-validation/default/shopify.extension.toml.liquid b/checkout/wasm/cart-checkout-validation/default/shopify.extension.toml.liquid index d3b1f766..2ec3c11a 100644 --- a/checkout/wasm/cart-checkout-validation/default/shopify.extension.toml.liquid +++ b/checkout/wasm/cart-checkout-validation/default/shopify.extension.toml.liquid @@ -1,4 +1,4 @@ -api_version = "2025-01" +api_version = "2025-07" [[extensions]] name = "t:name" @@ -8,12 +8,11 @@ type = "function" description = "t:description" [[extensions.targeting]] - target = "purchase.validation.run" - input_query = "run.graphql" - export = "run" + target = "cart.validations.generate.run" + input_query = "cart_validations_generate_run.graphql" + export = "cart_validations_generate_run" [extensions.build] command = "echo 'build the wasm'" path = "" watch = [] -