Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\router\schema;
18
 
19
use coding_exception;
20
use core\param;
21
use core\router\schema\objects\type_base;
22
use core\router\schema\response\response;
23
use stdClass;
24
 
25
/**
26
 * A generic part of the OpenAPI Schema object.
27
 *
28
 * @package    core
29
 * @copyright  2023 Andrew Lyons <andrew@nicols.co.uk>
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
abstract class openapi_base {
33
    /**
34
     * Base constructor which does nothing.
35
     *
36
     * We keep an $extra parameter here for future-proofing.
37
     * This allows named parameters to be used and allows contrib plugins to
38
     * make use of parameters in newer versions even if they don't exist in older versions.
39
     *
40
     * @param mixed ...$extra Extra arguments to allow for future versions of Moodle to add options without breaking plugins
41
     */
42
    public function __construct(
43
        mixed ...$extra,
44
    ) {
45
    }
46
 
47
    /**
48
     * Get the $ref for this class.
49
     *
50
     * @param bool $qualify Whether to qualify the reference with the #/components/ part.
51
     * @return string
52
     */
53
    public function get_reference(
54
        bool $qualify = true,
55
    ): string {
56
        return static::get_reference_for_class(
57
            classname: get_class($this),
58
            qualify: $qualify,
59
        );
60
    }
61
 
62
    /**
63
     * Get the OpenAPI data to include in the OpenAPI specification.
64
     *
65
     * @param specification $api
66
     * @param null|string $path
67
     * @return null|stdClass
68
     * @throws coding_exception
69
     */
70
    final public function get_openapi_schema(
71
        specification $api,
72
        ?string $path = null,
73
    ): ?stdClass {
74
        if (is_a($this, referenced_object::class)) {
75
            // This class is a referenced object, so we need to add it to the specification.
76
            if (!$api->is_reference_defined($this->get_reference())) {
77
                $api->add_component($this);
78
            }
79
 
80
            return (object) [
81
                '$ref' => $this->get_reference(),
82
            ];
83
        }
84
 
85
        return $this->get_openapi_description(
86
            api: $api,
87
            path: $path,
88
        );
89
    }
90
 
91
    /**
92
     * Get the OpenAPI data to include in the OpenAPI specification.
93
     *
94
     * @param specification $api
95
     * @param null|string $path
96
     * @return null|stdClass
97
     */
98
    abstract public function get_openapi_description(
99
        specification $api,
100
        ?string $path = null,
101
    ): ?stdClass;
102
 
103
    /**
104
     * Get the $ref a class name.
105
     *
106
     * https://swagger.io/docs/specification/using-ref/
107
     *
108
     * @param string $classname The class to get a reference for
109
     * @param bool $qualify Whether to qualify the reference with the #/components/ part
110
     * @return string The reference
111
     * @throws coding_exception
112
     */
113
    public static function get_reference_for_class(
114
        string $classname,
115
        bool $qualify = true,
116
    ): string {
117
        $reference = static::escape_reference($classname);
118
        if (!$qualify) {
119
            return $reference;
120
        }
121
 
122
        // Note: The following list must be kept in-sync with specification::add_component().
123
        return match (true) {
124
            is_a($classname, header_object::class, true) => static::get_reference_for_header($reference),
125
            is_a($classname, parameter::class, true) => static::get_reference_for_parameter($reference),
126
            is_a($classname, response::class, true) => static::get_reference_for_response($reference),
127
            is_a($classname, example::class, true) => static::get_reference_for_example($reference),
128
            is_a($classname, request_body::class, true) => static::get_reference_for_request_body($reference),
129
            is_a($classname, type_base::class, true) => static::get_reference_for_schema($reference),
130
            default => throw new coding_exception("Class {$classname} is not a schema."),
131
        };
132
    }
133
 
134
 
135
    /**
136
     * Get the qualified $ref for a parameter.
137
     *
138
     * @param string $reference
139
     * @return string
140
     */
141
    public static function get_reference_for_header(string $reference): string {
142
        return "#/components/headers/{$reference}";
143
    }
144
 
145
    /**
146
     * Get the qualified $ref for a parameter.
147
     *
148
     * @param string $reference
149
     * @return string
150
     */
151
    public static function get_reference_for_parameter(string $reference): string {
152
        return "#/components/parameters/{$reference}";
153
    }
154
 
155
    /**
156
     * Get the qualified $ref for a response.
157
     *
158
     * @param string $reference
159
     * @return string
160
     */
161
    public static function get_reference_for_response(string $reference): string {
162
        return "#/components/responses/{$reference}";
163
    }
164
 
165
    /**
166
     * Get the qualified $ref for an example.
167
     *
168
     * @param string $reference
169
     * @return string
170
     */
171
    public static function get_reference_for_example(string $reference): string {
172
        return "#/components/examples/{$reference}";
173
    }
174
 
175
    /**
176
     * Get the qualified $ref for a request body.
177
     *
178
     * @param string $reference
179
     * @return string
180
     */
181
    public static function get_reference_for_request_body(string $reference): string {
182
        return "#/components/requestBodies/{$reference}";
183
    }
184
 
185
    /**
186
     * Get the qualified $ref for a schema.
187
     *
188
     * @param string $reference
189
     * @return string
190
     */
191
    public static function get_reference_for_schema(string $reference): string {
192
        return "#/components/schemas/{$reference}";
193
    }
194
 
195
    /**
196
     * Escape a reference following rules defined at https://swagger.io/docs/specification/using-ref/.
197
     *
198
     * @param string $reference
199
     * @return string
200
     */
201
    public static function escape_reference(string $reference): string {
202
        // Note https://swagger.io/docs/specification/using-ref/ defines the following replacements:
203
        // ~ => ~0
204
        // / => ~1
205
        // We also add some other replacements:
206
        // \ => --
207
        // These must be used in all reference names.
208
        // See also https://spec.openapis.org/oas/v3.1.0#components-object
209
        // And the following regular expression:
210
        // ^[a-zA-Z0-9\.\-_]+$.
211
        return str_replace(
212
            ['~', '/', '\\'],
213
            ['~0', '~1', '--'],
214
            $reference,
215
        );
216
    }
217
 
218
    /**
219
     * Get the schema for a given type.
220
     *
221
     * @param param $type
222
     * @return stdClass
223
     */
224
    public function get_schema_from_type(param $type): stdClass {
225
        $data = new stdClass();
226
 
227
        $data->type = match ($type) {
228
            // OpenAPI uses an extension of the JSON Schema to define both integers and numbers (float).
229
            param::INT => 'integer',
230
            param::FLOAT => 'number',
231
            param::BOOL => 'boolean',
232
 
233
            // All other types are string types and most have a pattern.
234
            default => 'string',
235
        };
236
 
237
        if ($pattern = $type->get_clientside_expression()) {
238
            $data->pattern = $pattern;
239
        }
240
 
241
        return $data;
242
    }
243
}