Skip to content
wiki.fftac.org

Class Uai1 Validator - Source Excerpt 02

Back to Class Uai1 Validator

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-validator.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/uai-1-wordpress/includes/class-uai1-validator.php

if (isset($message['provenance']['confidence']) && (!is_numeric($message['provenance']['confidence']) || (float) $message['provenance']['confidence'] < 0 || (float) $message['provenance']['confidence'] > 1)) {
                $errors[] = self::issue('$.provenance.confidence', 'invalid_confidence', 'confidence must be a number between 0 and 1.');
            }
        }

        if (is_array($message['integrity'] ?? null)) {
            self::validate_required_object_fields(
                (array) $message['integrity'],
                '$.integrity',
                ['version', 'algorithm', 'canonicalization', 'checksum'],
                ['version', 'algorithm', 'canonicalization', 'checksum'],
                $errors
            );

            if (isset($message['integrity']['version']) && (!is_int($message['integrity']['version']) || (int) $message['integrity']['version'] < 1)) {
                $errors[] = self::issue('$.integrity.version', 'invalid_integrity_version', 'integrity.version must be an integer greater than zero.');
            }
        }

        if (is_array($message['extensions'] ?? null)) {
            foreach ((array) $message['extensions'] as $index => $extension) {
                if (!is_array($extension) || self::is_list($extension)) {
                    $errors[] = self::issue("$.extensions[{$index}]", 'invalid_type', 'Extension entries must be objects.');
                    continue;
                }

                self::validate_required_object_fields(
                    $extension,
                    "\$.extensions[{$index}]",
                    ['namespace', 'purpose', 'critical'],
                    ['namespace', 'purpose', 'critical'],
                    $errors
                );

                if (isset($extension['critical']) && !is_bool($extension['critical'])) {
                    $errors[] = self::issue("$.extensions[{$index}].critical", 'invalid_type', 'critical must be a boolean.');
                }
            }
        }

        if ($profile !== '' && isset(self::PROFILE_BODY_FIELDS[$profile]) && is_array($message['body'] ?? null)) {
            $contract = self::PROFILE_BODY_FIELDS[$profile];
            self::validate_required_object_fields((array) $message['body'], '$.body', $contract['required'], $contract['allowed'], $errors);
            self::validate_profile_body((array) $message['body'], $profile, $errors, $warnings);
        }

        return self::result(empty($errors), $profile, $errors, $warnings);
    }

    private static function validate_endpoint_party(array $value, string $path, array &$errors): void
    {
        self::validate_required_object_fields(
            $value,
            $path,
            ['type', 'id'],
            ['type', 'id', 'label', 'uri', 'did', 'role', 'implementation'],
            $errors
        );
    }

    private static function validate_profile_body(array $body, string $profile, array &$errors, array &$warnings): void
    {
        if ($profile === 'uai.intent.response.v1' && isset($body['status']) && !in_array($body['status'], ['ok', 'accepted', 'partial', 'failed'], true)) {
            $errors[] = self::issue('$.body.status', 'invalid_status', 'status must be ok, accepted, partial, or failed.');
        }

        if ($profile === 'uai.error.v1') {
            if (isset($body['status']) && (!is_int($body['status']) || (int) $body['status'] < 400 || (int) $body['status'] > 599)) {
                $errors[] = self::issue('$.body.status', 'invalid_error_status', 'status must be an HTTP error status from 400 through 599.');
            }

            if (isset($body['retryable']) && !is_bool($body['retryable'])) {
                $errors[] = self::issue('$.body.retryable', 'invalid_type', 'retryable must be a boolean.');
            }
        }

        if ($profile === 'uai.conformance.result.v1' && isset($body['status']) && !in_array($body['status'], ['pass', 'warn', 'fail'], true)) {
            $errors[] = self::issue('$.body.status', 'invalid_conformance_status', 'status must be pass, warn, or fail.');
        }

        if ($profile === 'uai.intent.request.v1') {
            if (isset($body['parameters']) && (!is_array($body['parameters']) || self::is_list($body['parameters']))) {
                $errors[] = self::issue('$.body.parameters', 'invalid_type', 'parameters must be an object.');
            }

            if (isset($body['constraints']) && (!is_array($body['constraints']) || !self::is_list($body['constraints']))) {
                $errors[] = self::issue('$.body.constraints', 'invalid_type', 'constraints must be a list.');
            }
        }

        if ($profile === 'uai.capability.statement.v1') {
            foreach (['operations', 'input_profiles', 'output_profiles', 'async_profiles', 'security_schemes', 'endpoints'] as $field) {
                if (isset($body[$field]) && (!is_array($body[$field]) || !self::is_list($body[$field]))) {
                    $errors[] = self::issue('$.body.' . $field, 'invalid_type', "{$field} must be a list.");
                }
            }

            if (isset($body['operations']) && is_array($body['operations']) && count($body['operations']) === 0) {
                $errors[] = self::issue('$.body.operations', 'empty_operations', 'operations must include at least one operation.');
            }

            if (isset($body['security_schemes']) && is_array($body['security_schemes'])) {
                foreach ($body['security_schemes'] as $index => $scheme) {
                    if (!is_array($scheme) || self::is_list($scheme)) {
                        $errors[] = self::issue("$.body.security_schemes[{$index}]", 'invalid_type', 'security_schemes entries must be objects.');
                        continue;
                    }

                    self::validate_required_object_fields($scheme, "\$.body.security_schemes[{$index}]", ['id', 'type', 'binding'], ['id', 'type', 'binding'], $errors);
                }
            }

            if (isset($body['endpoints']) && is_array($body['endpoints'])) {
                foreach ($body['endpoints'] as $index => $endpoint) {
                    if (!is_array($endpoint) || self::is_list($endpoint)) {
                        $errors[] = self::issue("$.body.endpoints[{$index}]", 'invalid_type', 'endpoints entries must be objects.');
                        continue;
                    }

                    self::validate_required_object_fields($endpoint, "\$.body.endpoints[{$index}]", ['kind', 'url', 'method'], ['kind', 'url', 'method'], $errors);
                }
            }
        }

        if (!empty($body['constraints']) && is_array($body['constraints']) && in_array('validator-ready', $body['constraints'], true)) {
            $warnings[] = self::issue('$.body.constraints', 'validator_policy_not_exhaustive', 'Local validation checks the published envelope shape but does not replace the UAIX public validator policy checks.');
        }
    }

    private static function validate_required_object_fields(array $value, string $path, array $required, array $allowed, array &$errors): void
    {
        foreach ($required as $field) {
            if (!array_key_exists($field, $value)) {
                $errors[] = self::issue($path . '.' . $field, 'missing_required', "Missing required field: {$field}");
            }
        }

        foreach (array_keys($value) as $field) {
            if (!in_array($field, $allowed, true)) {
                $errors[] = self::issue($path . '.' . $field, 'unknown_field', 'Field is not part of the current UAIX UAI-1 schema for this object.');
            }
        }
    }

    /**
     * @param mixed $value
     */
    private static function validate_type($value, string $type): string
    {
        if ($type === 'string') {
            return is_string($value) && trim($value) !== '' ? '' : 'expected a non-empty string.';
        }

        if ($type === 'object') {
            if (!is_array($value) || self::is_list($value)) {
                return 'expected an object.';
            }

            return '';
        }

        if ($type === 'list') {
            if (!is_array($value) || !self::is_list($value)) {
                return 'expected a list.';
            }

            return '';
        }

        return '';
    }

    private static function issue(string $path, string $code, string $message): array
    {
        return [
            'path' => $path,
            'code' => $code,
            'message' => $message,
        ];
    }

    private static function result(bool $valid, string $profile, array $errors, array $warnings): array
    {
        return [
            'valid' => $valid,
            'profile' => $profile,
            'errors' => $errors,