Automattic\WooCommerce\Internal\Abilities\REST

RestAbilityFactory::relax_output_schema_for_wc_quirksprivate staticWC 1.0

Recursively relax an output schema so it accepts the shapes WooCommerce REST controllers actually return.

Used on output schemas only — input schemas keep their tighter constraints so MCP clients still get useful hints when formatting tool calls. Must run AFTER {@see self::sanitize_schema()}, which converts the date-time pseudo-type to type: "string" + format: "date-time" — this method then strips the format.

Relaxations:

  1. format: "date-time" and format: "uri" are stripped. WooCommerce REST
    date strings (e.g. `2025-11-24T16:31:43`) omit the timezone suffix RFC 3339
    requires, and `format: "uri"` fields routinely return empty strings.
  2. Any type whose declared members are all scalars and/or null is
    widened to {@see self::OUTPUT_SCALAR_UNION} — every JSON type plus
    `null`. Applies to single scalars (`string`, `integer`, `number`,
    `boolean`) and to pre-existing unions like `[integer, null]`. Fields
    that declare any compound type (`object`, `array`) are left alone.
    This is a deliberate accuracy tradeoff: many WooCommerce REST
    controllers declare types that disagree with what they actually return
    (e.g. `shipping_class_id` declared `string` but returned as `int`;
    `low_stock_amount` declared `[integer, null]` but returned as `""`
    when unset; `meta_data[].display_value` declared `string` but
    routinely an array for variation attributes and serialized custom
    meta). The alternative is per-controller schema fixes across legacy
    code. Skipped inside `anyOf` / `oneOf` / `allOf` branches: widening
    every branch breaks the "exactly one" rule for `oneOf`, and for
    `anyOf` / `allOf` the schema author was explicit about admissible
    shapes.

Recurses into properties, items (single schema and tuple form), additionalProperties, and the anyOf / oneOf / allOf combiners.

Method of the class: RestAbilityFactory{}

No Hooks.

Returns

Array. Relaxed schema node.

Usage

$result = RestAbilityFactory::relax_output_schema_for_wc_quirks( $schema, $apply_null_union ): array;
$schema(array) (required)
A JSON Schema node.
$apply_null_union(true|false)
Whether to apply the scalar-to-nullable widening at this node. False when recursing into combiner branches.
Default: true

RestAbilityFactory::relax_output_schema_for_wc_quirks() code WC 10.9.4

private static function relax_output_schema_for_wc_quirks( array $schema, bool $apply_null_union = true ): array {
	if ( isset( $schema['format'] ) && in_array( $schema['format'], array( 'date-time', 'uri' ), true ) ) {
		unset( $schema['format'] );
	}

	if ( $apply_null_union && isset( $schema['type'] ) && self::should_widen_to_output_union( $schema['type'] ) ) {
		$schema['type'] = self::OUTPUT_SCALAR_UNION;
	}

	if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
		foreach ( $schema['properties'] as $key => $property ) {
			if ( is_array( $property ) ) {
				$schema['properties'][ $key ] = self::relax_output_schema_for_wc_quirks( $property, $apply_null_union );
			}
		}
	}

	if ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
		if ( isset( $schema['items'][0] ) ) {
			// Tuple form: each numerically-indexed entry validates the array element at that position.
			foreach ( $schema['items'] as $index => $entry ) {
				if ( is_array( $entry ) ) {
					$schema['items'][ $index ] = self::relax_output_schema_for_wc_quirks( $entry, $apply_null_union );
				}
			}
		} else {
			$schema['items'] = self::relax_output_schema_for_wc_quirks( $schema['items'], $apply_null_union );
		}
	}

	if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) {
		$schema['additionalProperties'] = self::relax_output_schema_for_wc_quirks( $schema['additionalProperties'], $apply_null_union );
	}

	foreach ( array( 'anyOf', 'oneOf', 'allOf' ) as $combiner ) {
		if ( isset( $schema[ $combiner ] ) && is_array( $schema[ $combiner ] ) ) {
			foreach ( $schema[ $combiner ] as $index => $branch ) {
				if ( is_array( $branch ) ) {
					// Entering a combiner from anywhere disables null-union for the entire subtree.
					$schema[ $combiner ][ $index ] = self::relax_output_schema_for_wc_quirks( $branch, false );
				}
			}
		}
	}

	return $schema;
}