Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
57 / 76
90.00% covered (success)
90.00%
9 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ZResponseEnvelope
75.00% covered (warning)
75.00%
57 / 76
90.00% covered (success)
90.00%
9 / 10
52.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDefinition
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 isValid
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getZValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getZMetadata
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrors
36.67% covered (danger)
36.67%
11 / 30
0.00% covered (danger)
0.00%
0 / 1
121.61
 setMetaDataValue
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 wrapInResponseMap
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 wrapErrorInResponseMap
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * WikiLambda ZResponseEnvelope
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\ZObjects;
12
13use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
14
15class ZResponseEnvelope extends ZObject {
16
17    /**
18     * Construct a new ZResponseEnvelope instance
19     *
20     * @param ?ZObject $response Value of the response
21     * @param ?ZObject $metadata Meta-data response
22     */
23    public function __construct( $response, $metadata ) {
24        $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_VALUE ] = $response ?? (object)ZTypeRegistry::Z_VOID_INSTANCE;
25        $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ] = $metadata ?? (object)ZTypeRegistry::Z_VOID_INSTANCE;
26    }
27
28    /**
29     * @inheritDoc
30     */
31    public static function getDefinition(): array {
32        return [
33            'type' => [
34                'type' => ZTypeRegistry::Z_REFERENCE,
35                'value' => ZTypeRegistry::Z_RESPONSEENVELOPE,
36            ],
37            'keys' => [
38                ZTypeRegistry::Z_RESPONSEENVELOPE_VALUE => [
39                    'type' => ZTypeRegistry::Z_OBJECT
40                ],
41                ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA => [
42                    'type' => ZTypeRegistry::Z_OBJECT
43                ],
44            ],
45        ];
46    }
47
48    /**
49     * @inheritDoc
50     */
51    public function isValid(): bool {
52        return (
53            ( $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_VALUE ] instanceof ZObject &&
54                $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_VALUE ]->isValid() ) ||
55            ( $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ] instanceof ZObject &&
56                $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ]->isValid() )
57        );
58    }
59
60    /**
61     * Get the value part of the response envelope
62     *
63     * @return ZObject
64     */
65    public function getZValue() {
66        return $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_VALUE ];
67    }
68
69    /**
70     * Get the Meta-data part of the response envelope
71     *
72     * TODO (T307483): This should ideally be type-hinted as a PHP implementation of ZMap,
73     * so that it'd be much easier to interact with.
74     *
75     * @return ZObject
76     */
77    public function getZMetadata() {
78        return $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ];
79    }
80
81    /**
82     * Does the meta-data in the response envelope have one or more fatal errors?
83     *
84     * @return bool
85     */
86    public function hasErrors(): bool {
87        return $this->getErrors() !== null;
88    }
89
90    /**
91     * Does the meta-data in the response envelope have one or more fatal errors?
92     *
93     * @return ZError|null
94     */
95    public function getErrors(): ?ZError {
96        $metaData = $this->getZMetadata();
97
98        if ( !$metaData || !is_object( $metaData ) ) {
99            return null;
100        }
101
102        if ( $metaData instanceof ZObject ) {
103            if ( $metaData instanceof ZTypedMap ) {
104                // @phan-suppress-next-line PhanTypeMismatchReturnSuperType phan can't tell this must be a ZError
105                return $metaData->getValueGivenKey( new ZString( 'errors' ) );
106            }
107
108            if ( $metaData instanceof ZReference && $metaData->getZValue() === ZTypeRegistry::Z_VOID ) {
109                return null;
110            }
111        }
112
113        // A this point, we know we've been initialised with a raw object, presumably in the form of a ZTypedMap.
114
115        if ( !property_exists( $metaData, ZTypeRegistry::Z_OBJECT_TYPE ) ) {
116            return null;
117        }
118
119        // @phan-suppress-next-line PhanUndeclaredProperty phan can't tell this must exist per line #115
120        $metaDataType = $metaData->{ ZTypeRegistry::Z_OBJECT_TYPE };
121
122        if ( $metaDataType === ZTypeRegistry::Z_UNIT ) {
123            return null;
124        }
125
126        if (
127            !is_object( $metaDataType ) ||
128            !property_exists( $metaDataType, ZTypeRegistry::Z_OBJECT_TYPE ) ||
129            $metaDataType->{ ZTypeRegistry::Z_OBJECT_TYPE } !== ZTypeRegistry::Z_FUNCTIONCALL ||
130            !property_exists( $metaDataType, ZTypeRegistry::Z_FUNCTIONCALL_FUNCTION ) ||
131            $metaDataType->{ ZTypeRegistry::Z_FUNCTIONCALL_FUNCTION } !== 'Z883' ||
132            !property_exists( $metaDataType, 'Z883K1' ) ||
133            $metaDataType->{ 'Z883K1' } !== 'Z6' ||
134            !property_exists( $metaDataType, 'Z883K2' ) ||
135            $metaDataType->{ 'Z883K2' } !== 'Z1'
136        ) {
137            // Meta-data map is a non-object or otherwise wrong; something's gone wrong, return nothing.
138            return null;
139        }
140
141        '@phan-var \stdClass $metaData';
142        $metaDataMapContents = $metaData->{ 'K1' };
143
144        while ( property_exists( $metaDataMapContents, 'K1' ) ) {
145            $currentValue = $metaDataMapContents->{ 'K1' };
146
147            if ( $currentValue->{ 'K1' } === 'errors' ) {
148                return $currentValue->{ 'K2' };
149            }
150
151            // Not found in this value, so recurse to the next value in the map
152            $metaDataMapContents = $metaDataMapContents->{ 'K2' };
153        }
154
155        // Nothing found in the map, so there are no errors
156        return null;
157    }
158
159    /**
160     * Set the meta-data in the response envelope to the given key.
161     *
162     * @param ZString|string $key The key to use
163     * @param ZObject $value The value to set
164     */
165    public function setMetaDataValue( $key, ZObject $value ) {
166        $metaData = $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ];
167
168        if ( !( $metaData instanceof ZTypedMap ) ) {
169            $metaData = self::wrapInResponseMap( $key, $value );
170        } else {
171            $key = $key instanceof ZString ? $key : new ZString( $key );
172            $metaData->setValueForKey( $key, $value );
173        }
174
175        $this->data[ ZTypeRegistry::Z_RESPONSEENVELOPE_METADATA ] = $metaData;
176    }
177
178    /**
179     * Convenience method to make a ZTypedMap for a ZResponseEnvelope with a
180     * given key/value pair
181     *
182     * @param string $key The key to use
183     * @param ZObject $value The value to set
184     * @return ZTypedMap
185     */
186    public static function wrapInResponseMap( $key, $value ): ZTypedMap {
187        $pairType = ZTypedPair::buildType( 'Z6', 'Z1' );
188
189        return new ZTypedMap(
190            ZTypedMap::buildType( 'Z6', 'Z1' ),
191            new ZTypedList(
192                ZTypedList::buildType( $pairType ),
193                [
194                    new ZTypedPair(
195                        $pairType,
196                        new ZString( $key ),
197                        $value
198                    ),
199                ]
200            )
201        );
202    }
203
204    /**
205     * Convenience method to make a ZTypedMap for a ZResponseEnvelope for a ZError
206     *
207     * @param ZError $error The error to set
208     * @return ZTypedMap
209     */
210    public static function wrapErrorInResponseMap( $error ): ZTypedMap {
211        return self::wrapInResponseMap( 'errors', $error );
212    }
213}