Skip to content

Commit 2bad05b

Browse files
committed
feat: docs sdk
1 parent ac31311 commit 2bad05b

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

example.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Appwrite\SDK\Language\Android;
2222
use Appwrite\SDK\Language\Kotlin;
2323
use Appwrite\SDK\Language\ReactNative;
24+
use Appwrite\SDK\Language\Docs;
2425

2526
try {
2627

@@ -266,6 +267,13 @@ function configureSDK($sdk, $overrides = []) {
266267
configureSDK($sdk);
267268
$sdk->generate(__DIR__ . '/examples/graphql');
268269
}
270+
271+
// Docs
272+
if (!$requestedSdk || $requestedSdk === 'docs') {
273+
$sdk = new SDK(new Docs(), new Swagger2($spec));
274+
configureSDK($sdk);
275+
$sdk->generate(__DIR__ . '/examples/docs');
276+
}
269277
}
270278
catch (Exception $exception) {
271279
echo 'Error: ' . $exception->getMessage() . ' on ' . $exception->getFile() . ':' . $exception->getLine() . "\n";

src/SDK/Language/Docs.php

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
3+
namespace Appwrite\SDK\Language;
4+
5+
use Appwrite\SDK\Language;
6+
use Twig\TwigFilter;
7+
8+
class Docs extends Language
9+
{
10+
protected $params = [];
11+
12+
/**
13+
* @return string
14+
*/
15+
public function getName(): string
16+
{
17+
return 'Docs';
18+
}
19+
20+
/**
21+
* @return array
22+
*/
23+
public function getKeywords(): array
24+
{
25+
// Docs don't need keyword escaping
26+
return [];
27+
}
28+
29+
/**
30+
* @return array
31+
*/
32+
public function getIdentifierOverrides(): array
33+
{
34+
return [];
35+
}
36+
37+
public function getStaticAccessOperator(): string
38+
{
39+
return '.';
40+
}
41+
42+
public function getStringQuote(): string
43+
{
44+
return "'";
45+
}
46+
47+
public function getArrayOf(string $elements): string
48+
{
49+
return '[' . $elements . ']';
50+
}
51+
52+
/**
53+
* @return array
54+
*/
55+
public function getFiles(): array
56+
{
57+
return [
58+
[
59+
'scope' => 'method',
60+
'destination' => 'typescript/{{ service.name | caseLower }}/{{ method.name | caseKebab }}.md',
61+
'template' => 'docs/typescript/method.md.twig',
62+
]
63+
];
64+
}
65+
66+
/**
67+
* @param array $parameter
68+
* @return string
69+
*/
70+
public function getTypeName(array $parameter, array $spec = []): string
71+
{
72+
// For TypeScript/JavaScript-like languages
73+
if (isset($parameter['enumName'])) {
74+
return \ucfirst($parameter['enumName']);
75+
}
76+
if (!empty($parameter['enumValues'])) {
77+
return \ucfirst($parameter['name']);
78+
}
79+
if (isset($parameter['items'])) {
80+
$parameter['array'] = $parameter['items'];
81+
}
82+
switch ($parameter['type']) {
83+
case self::TYPE_INTEGER:
84+
case self::TYPE_NUMBER:
85+
return 'number';
86+
case self::TYPE_ARRAY:
87+
if (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) {
88+
return $this->getTypeName($parameter['array']) . '[]';
89+
}
90+
return 'any[]';
91+
case self::TYPE_FILE:
92+
return 'File';
93+
case self::TYPE_OBJECT:
94+
return 'object';
95+
}
96+
return $parameter['type'];
97+
}
98+
99+
/**
100+
* @param array $param
101+
* @return string
102+
*/
103+
public function getParamDefault(array $param): string
104+
{
105+
$type = $param['type'] ?? '';
106+
$default = $param['default'] ?? '';
107+
$required = $param['required'] ?? false;
108+
109+
if ($required) {
110+
return '';
111+
}
112+
113+
if (!empty($default)) {
114+
return ' = ' . $default;
115+
}
116+
117+
return match ($type) {
118+
self::TYPE_ARRAY => ' = []',
119+
self::TYPE_OBJECT => ' = {}',
120+
default => ' = null',
121+
};
122+
}
123+
124+
/**
125+
* @param array $param
126+
* @param string $lang
127+
* @return string
128+
*/
129+
public function getParamExample(array $param, string $lang = ''): string
130+
{
131+
$type = $param['type'] ?? '';
132+
$example = $param['example'] ?? '';
133+
134+
$hasExample = !empty($example) || $example === 0 || $example === false;
135+
136+
if (!$hasExample) {
137+
return match ($type) {
138+
self::TYPE_ARRAY => '[]',
139+
self::TYPE_FILE => 'file',
140+
self::TYPE_INTEGER, self::TYPE_NUMBER => '0',
141+
self::TYPE_BOOLEAN => 'false',
142+
self::TYPE_OBJECT => '{}',
143+
self::TYPE_STRING => "''",
144+
};
145+
}
146+
147+
return match ($type) {
148+
self::TYPE_ARRAY => $this->isPermissionString($example) ? $this->getPermissionExample($example) : $example,
149+
self::TYPE_INTEGER, self::TYPE_NUMBER => (string)$example,
150+
self::TYPE_FILE => 'file',
151+
self::TYPE_BOOLEAN => ($example) ? 'true' : 'false',
152+
self::TYPE_OBJECT => ($example === '{}')
153+
? '{}'
154+
: (($formatted = json_encode(json_decode($example, true), JSON_PRETTY_PRINT))
155+
? $formatted
156+
: $example),
157+
self::TYPE_STRING => "'{$example}'",
158+
};
159+
}
160+
161+
public function getPermissionExample(string $example): string
162+
{
163+
$permissions = $this->extractPermissionParts($example);
164+
$result = [];
165+
166+
foreach ($permissions as $permission) {
167+
$action = ucfirst($permission['action']);
168+
$role = ucfirst($this->toCamelCase($permission['role']));
169+
170+
if ($permission['id'] !== null) {
171+
if ($permission['innerRole'] !== null) {
172+
$result[] = "Permission.{$action}(Role.{$role}('{$permission['id']}', '{$permission['innerRole']}'))";
173+
} else {
174+
$result[] = "Permission.{$action}(Role.{$role}('{$permission['id']}'))";
175+
}
176+
} else {
177+
$result[] = "Permission.{$action}(Role.{$role}())";
178+
}
179+
}
180+
181+
return '[' . implode(', ', $result) . ']';
182+
}
183+
184+
public function getFilters(): array
185+
{
186+
return [
187+
new TwigFilter('getPropertyType', function ($value, $method = []) {
188+
return $this->getTypeName($value, $method);
189+
}),
190+
new TwigFilter('comment', function ($value) {
191+
$value = explode("\n", $value);
192+
foreach ($value as $key => $line) {
193+
$value[$key] = wordwrap($line, 80, "\n");
194+
}
195+
return implode("\n", $value);
196+
}, ['is_safe' => ['html']]),
197+
new TwigFilter('caseEnumKey', function (string $value) {
198+
return $this->toPascalCase($value);
199+
}),
200+
new TwigFilter('getResponseModel', function (array $method) {
201+
if (!empty($method['responseModel']) && $method['responseModel'] !== 'any') {
202+
return 'Models.' . \ucfirst($method['responseModel']);
203+
}
204+
return null;
205+
}),
206+
];
207+
}
208+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# {{ method.name | caseCamel }}
2+
{% if method.deprecated %}
3+
4+
⚠️ **DEPRECATED**{% if method.since %} since {{ method.since }}{% endif %}{% if method.replaceWith %} - Use `{{ method.replaceWith }}` instead{% endif %}
5+
6+
{% endif %}
7+
8+
Description: {{ method.description | raw | trim }}
9+
10+
## Parameters
11+
{% if method.parameters.all | length > 0 %}
12+
13+
| Parameter | Type | Required | Description |
14+
|-----------|------|----------|-------------|
15+
{% for parameter in method.parameters.all -%}
16+
| `{{ parameter.name | caseCamel }}` | `{{ parameter | getPropertyType }}` | {% if parameter.required %}✅{% else %}❌{% endif %} | {{ parameter.description | raw }}{% if parameter.default %} (Default: `{{ parameter.default }}`){% endif %}{% if parameter.enumValues | length > 0 %}<br>**Allowed:** {% for value in parameter.enumValues %}`{{ value }}`{% if not loop.last %}, {% endif %}{% endfor %}{% endif %} |
17+
{% endfor %}
18+
{%- else %}
19+
20+
This method does not accept any parameters.
21+
{% endif %}
22+
23+
## Usage
24+
25+
```typescript
26+
import { Client, {{ service.name | caseUcfirst }}{% for parameter in method.parameters.all %}{% if parameter.enumValues | length > 0 %}, {{ parameter.enumName | caseUcfirst }}{% endif %}{% endfor %}{% if method | getResponseModel %}, Models{% endif %} } from '{{ spec.title | caseLower }}';
27+
28+
const client = new Client()
29+
.setEndpoint('{{ spec.endpointDocs | raw }}')
30+
{%- if method.auth|length > 0 %}
31+
{% for node in method.auth -%}
32+
{% for key,header in node|keys -%}
33+
.set{{header}}('{{ node[header]['x-appwrite']['demo'] | raw }}')
34+
{%- endfor %}
35+
{%- endfor -%}
36+
{% endif -%}
37+
;
38+
39+
const {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client);
40+
{% if method.type == 'location' %}
41+
const result{% if method | getResponseModel %}: {{ method | getResponseModel }}{% endif %} = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});
42+
{% else -%}
43+
{
44+
{% for parameter in method.parameters.all %}
45+
{{ parameter.name | caseCamel }}: {% if parameter.enumValues | length > 0 %}{{ parameter.enumName | caseUcfirst }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %},
46+
{% endfor -%}
47+
});
48+
{% endif -%}
49+
{% elseif method.type != 'webAuth' %}
50+
const result{% if method | getResponseModel %}: {{ method | getResponseModel }}{% endif %} = await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});
51+
{% else -%}
52+
{
53+
{% for parameter in method.parameters.all %}
54+
{{ parameter.name | caseCamel }}: {% if parameter.enumValues | length > 0 %}{{ parameter.enumName | caseUcfirst }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %},
55+
{% endfor -%}
56+
});
57+
{% endif -%}
58+
{% else -%}
59+
{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});
60+
{% else -%}
61+
{
62+
{% for parameter in method.parameters.all %}
63+
{{ parameter.name | caseCamel }}: {% if parameter.enumValues | length > 0 %}{{ parameter.enumName | caseUcfirst }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %},
64+
{% endfor -%}
65+
});
66+
{% endif -%}
67+
{% endif -%}
68+
```
69+
{% if method | getResponseModel %}
70+
71+
## Response Model
72+
73+
Returns a `{{ method | getResponseModel }}` object with the following properties:
74+
{% if method.responseModel and spec.definitions[method.responseModel] %}
75+
76+
| Property | Type | Description |
77+
|----------|------|-------------|
78+
{% for property in spec.definitions[method.responseModel].properties -%}
79+
| `{{ property.name | caseCamel }}` | `{{ property | getPropertyType }}` | {{ property.description | raw }} |
80+
{% endfor %}
81+
{%- endif %}
82+
{% endif %}

0 commit comments

Comments
 (0)