Skip to content

Commit fffd00f

Browse files
Merge pull request #942 from appwrite/feat-go-multipart
feat: go multipart
2 parents e95b918 + 6913e18 commit fffd00f

File tree

18 files changed

+317
-107
lines changed

18 files changed

+317
-107
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ jobs:
2626
DotNet80,
2727
FlutterStable,
2828
FlutterBeta,
29-
Go112,
30-
Go118,
29+
Go122,
3130
KotlinJava8,
3231
KotlinJava11,
3332
KotlinJava17,

src/SDK/Language/Go.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ public function getFiles(): array
9090
],
9191
[
9292
'scope' => 'default',
93-
'destination' => 'file/inputFile.go',
94-
'template' => 'go/inputFile.go.twig',
93+
'destination' => 'payload/payload.go',
94+
'template' => 'go/payload.go.twig',
9595
],
9696
[
9797
'scope' => 'default',
@@ -141,10 +141,12 @@ public function getTypeName(array $parameter, array $spec = []): string
141141
if (str_contains($parameter['description'] ?? '', 'Collection attributes') || str_contains($parameter['description'] ?? '', 'List of attributes')) {
142142
return '[]map[string]any';
143143
}
144+
144145
return match ($parameter['type']) {
145146
self::TYPE_INTEGER => 'int',
146147
self::TYPE_NUMBER => 'float64',
147-
self::TYPE_FILE => 'file.InputFile',
148+
self::TYPE_PAYLOAD,
149+
self::TYPE_FILE => '*payload.Payload',
148150
self::TYPE_STRING => 'string',
149151
self::TYPE_BOOLEAN => 'bool',
150152
self::TYPE_OBJECT => 'interface{}',
@@ -241,8 +243,11 @@ public function getParamExample(array $param): string
241243
case self::TYPE_ARRAY:
242244
$output .= '[]interface{}{}';
243245
break;
246+
case self::TYPE_PAYLOAD:
247+
$output .= 'payload.NewPayloadFromString("<BODY>")';
248+
break;
244249
case self::TYPE_FILE:
245-
$output .= 'file.NewInputFile("/path/to/file.png", "file.png")';
250+
$output .= 'payload.NewPayloadFromPath("/path/to/file.png", "file.png")';
246251
break;
247252
}
248253
} else {
@@ -267,10 +272,13 @@ public function getParamExample(array $param): string
267272
$output .= ($example) ? 'true' : 'false';
268273
break;
269274
case self::TYPE_STRING:
270-
$output .= "\"{$example}\"";
275+
$output .= '"{$example}"';
276+
break;
277+
case self::TYPE_PAYLOAD:
278+
$output .= 'payload.NewPayloadFromString("<BODY>")';
271279
break;
272280
case self::TYPE_FILE:
273-
$output .= 'file.NewInputFile("/path/to/file.png", "file.png")';
281+
$output .= 'payload.NewPayloadFromPath("/path/to/file.png", "file.png")';
274282
break;
275283
}
276284
}
@@ -304,6 +312,10 @@ public function getFilters(): array
304312

305313
protected function getPropertyType(array $property, array $spec, string $generic = 'map[string]interface{}'): string
306314
{
315+
316+
if (strpos($property['description'], 'HTTP response body. This will return empty unless execution') !== false) {
317+
return '*payload.Payload';
318+
}
307319
if (\array_key_exists('sub_schema', $property)) {
308320
$type = $this->toPascalCase($property['sub_schema']);
309321

templates/go/README.md.twig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ go get github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName }}
3636
* Then inject these environment variables:
3737

3838
```bash
39-
export YOUR_ENDPOINT=https://{{ sdk.gitUserName|url_encode }}.io/v1
40-
export YOUR_PROJECT_ID=6…8
41-
export YOUR_KEY="7055781…cd95"
42-
export COLLECTION_ID=616a095b20180
39+
export YOUR_ENDPOINT=https://{{ sdk.gitUserName|url_encode }}.io/v1
40+
export YOUR_PROJECT_ID=6…8
41+
export YOUR_KEY="7055781…cd95"
42+
export COLLECTION_ID=616a095b20180
4343
```
4444

4545
Create `main.go` file with:
@@ -63,7 +63,7 @@ func main() {
6363
appwrite.WithKey(os.Getenv("YOUR_KEY")),
6464
)
6565

66-
databases := appwrite.NewDatabase(client)
66+
databases := appwrite.NewDatabases(client)
6767

6868
data := map[string]string{
6969
"hello": "world",
@@ -84,8 +84,8 @@ func main() {
8484

8585
* After that, run the following
8686

87-
> % go run main.go
88-
> 2021/10/16 03:41:17 Created document: map[$collection:616a095b20180 $id:616a2dbd4df16 $permissions:map[read:[] write:[]] hello:world]
87+
> % go run main.go
88+
> 2021/10/16 03:41:17 Created document: map[$collection:616a095b20180 $id:616a2dbd4df16 $permissions:map[read:[] write:[]] hello:world]
8989

9090
{% if sdk.gettingStarted %}
9191

templates/go/base/params.twig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
params["{{ parameter.name }}"] = {{ parameter.name | caseUcfirst }}
1111
{% else %}
1212
if options.enabledSetters["{{ parameter.name | caseUcfirst}}"] {
13+
{%~ if parameter.type == "payload" %}
14+
params["{{ parameter.name }}"] = string(options.{{ parameter.name | caseUcfirst }}.Data)
15+
{%~ else %}
1316
params["{{ parameter.name }}"] = options.{{ parameter.name | caseUcfirst }}
17+
{%~ endif %}
1418
}
1519
{% endif %}
1620
{% endfor %}

templates/go/base/requests/api.twig

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
return nil, err
44
}
55
if strings.HasPrefix(resp.Type, "application/json") {
6-
bytes := []byte(resp.Result.(string))
6+
bytesData := []byte(resp.Result.(string))
77

88
{%~ if method | returnType(spec, spec.title | caseLower) != 'interface{}' and method | returnType(spec, spec.title | caseLower) != '[]byte' and method | returnType(spec, spec.title | caseLower) != 'bool' %}
9-
parsed := {{ method | returnType(spec, spec.title | caseLower) }}{}.New(bytes)
9+
parsed := {{ method | returnType(spec, spec.title | caseLower) }}{}.New(bytesData)
1010

11-
err = json.Unmarshal(bytes, parsed)
11+
err = json.Unmarshal(bytesData, parsed)
1212
if err != nil {
1313
return nil, err
1414
}
@@ -17,13 +17,16 @@
1717
{%~ else %}
1818
var parsed {{ method | returnType(spec, spec.title | caseLower) }}
1919

20-
err = json.Unmarshal(bytes, &parsed)
20+
err = json.Unmarshal(bytesData, &parsed)
2121
if err != nil {
2222
return nil, err
2323
}
2424
return &parsed, nil
2525
{%~ endif %}
2626
}
27+
{% if 'multipart/form-data' in method.consumes and method.type != "upload" %}
28+
{{ include('go/base/requests/execution.twig') }}
29+
{%~ endif %}
2730
var parsed {{ method | returnType(spec, spec.title | caseLower) }}
2831
parsed, ok := resp.Result.({{ method | returnType(spec, spec.title | caseLower) }})
2932
if !ok {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
if strings.Contains(resp.Type, "multipart/form-data") {
2+
bytesData, ok := resp.Result.([]byte)
3+
4+
if !ok {
5+
return nil, errors.New("unexpected response type")
6+
}
7+
responseData := string(bytesData)
8+
9+
matches := regexp.MustCompile("(-+\\w+)--").FindStringSubmatch(responseData)
10+
11+
if len(matches) != 2 {
12+
return nil, errors.New("unexpected response type")
13+
}
14+
15+
parts := strings.Split(responseData, matches[1])
16+
17+
if len(parts) == 0 {
18+
return nil, errors.New("unexpected response type")
19+
}
20+
execution := make(map[string]string, 10)
21+
22+
for _, part := range parts {
23+
cleanPart := strings.TrimSpace(part)
24+
partName := regexp.MustCompile("name=\"?(\\w+)").FindStringSubmatch(cleanPart)
25+
26+
if len(partName) != 2 {
27+
continue
28+
}
29+
30+
name := strings.TrimSpace(partName[1])
31+
lines := strings.Split(strings.ReplaceAll(cleanPart, "\r\n", "\n"), "\n")
32+
33+
Inner:
34+
for i, line := range lines[1:] {
35+
if line == "" {
36+
continue
37+
}
38+
39+
if line == "Content-Type: application/json" {
40+
for _, line := range lines[i:] {
41+
if line == "" {
42+
continue
43+
}
44+
45+
execution[name] = line
46+
}
47+
continue Inner
48+
}
49+
execution[name] += line + "\r\n"
50+
}
51+
execution[name] = strings.TrimSuffix(execution[name],"\r\n")
52+
}
53+
54+
statusCode, err := strconv.Atoi(execution["responseStatusCode"])
55+
if err != nil {
56+
statusCode = 0
57+
}
58+
59+
duration, err := strconv.ParseFloat(execution["duration"], 64)
60+
if err != nil {
61+
duration = 0.0
62+
}
63+
64+
var requestHeaders []models.Headers
65+
var responseHeaders []models.Headers
66+
67+
buffer := bytes.NewBuffer([]byte(execution["requestHeaders"]))
68+
decoder := json.NewDecoder(buffer)
69+
_ = decoder.Decode(&requestHeaders)
70+
71+
buffer = bytes.NewBuffer([]byte(execution["responseHeaders"]))
72+
decoder = json.NewDecoder(buffer)
73+
_ = decoder.Decode(&responseHeaders)
74+
75+
results := models.Execution{
76+
FunctionId: execution["functionId"],
77+
Trigger: execution["trigger"],
78+
Status: execution["status"],
79+
RequestMethod: execution["requestMethod"],
80+
RequestPath: execution["requestPath"],
81+
RequestHeaders: requestHeaders,
82+
ResponseStatusCode: statusCode,
83+
ResponseBody: payload.NewPayloadFromString(execution["responseBody"]),
84+
ResponseHeaders: responseHeaders,
85+
Logs: execution["logs"],
86+
Errors: execution["errors"],
87+
Duration: duration,
88+
ScheduledAt: execution["scheduledAt"],
89+
}
90+
91+
return &results, nil
92+
}

templates/go/client.go.twig

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"time"
2020
"runtime"
2121

22-
"github.com/{{sdk.gitUserName}}/sdk-for-go/file"
22+
"github.com/{{sdk.gitUserName}}/sdk-for-go/payload"
2323
)
2424

2525
const (
@@ -122,7 +122,7 @@ func (client *Client) AddHeader(key string, value string) {
122122
client.Headers[key] = value
123123
}
124124

125-
func isFileUpload(headers map[string]interface{}) bool {
125+
func isMultipart(headers map[string]interface{}) bool {
126126
contentType, ok := headers["content-type"].(string)
127127
if ok {
128128
return strings.Contains(strings.ToLower(contentType), "multipart/form-data")
@@ -131,13 +131,13 @@ func isFileUpload(headers map[string]interface{}) bool {
131131
}
132132

133133
func (client *Client) FileUpload(url string, headers map[string]interface{}, params map[string]interface{}, paramName string, uploadId string) (*ClientResponse, error) {
134-
inputFile, ok := params[paramName].(file.InputFile)
134+
payload, ok := params[paramName].(*payload.Payload)
135135
if !ok {
136-
msg := fmt.Sprintf("invalid input file. params[%s] must be of type file.InputFile", paramName)
136+
msg := fmt.Sprintf("invalid input file. params[%s] must be of type payload.Payload", paramName)
137137
return nil, errors.New(msg)
138138
}
139139

140-
file, err := os.Open(inputFile.Path)
140+
file, err := os.Open(payload.Path)
141141
if err != nil {
142142
return nil, err
143143
}
@@ -148,7 +148,7 @@ func (client *Client) FileUpload(url string, headers map[string]interface{}, par
148148
return nil, err
149149
}
150150

151-
inputFile.Data = make([]byte, client.ChunkSize)
151+
payload.Data = make([]byte, client.ChunkSize)
152152

153153
var result *ClientResponse
154154

@@ -168,12 +168,12 @@ func (client *Client) FileUpload(url string, headers map[string]interface{}, par
168168
if uploadId != "" && uploadId != "unique()" {
169169
headers["x-appwrite-id"] = uploadId
170170
}
171-
inputFile.Data = make([]byte, fileInfo.Size())
172-
_, err := file.Read(inputFile.Data)
171+
payload.Data = make([]byte, fileInfo.Size())
172+
_, err := file.Read(payload.Data)
173173
if err != nil && err != io.EOF {
174174
return nil, err
175175
}
176-
params[paramName] = inputFile
176+
params[paramName] = payload
177177

178178
result, err = client.Call("POST", url, headers, params)
179179
if err != nil {
@@ -196,13 +196,13 @@ func (client *Client) FileUpload(url string, headers map[string]interface{}, par
196196
offset := int64(i) * chunkSize
197197
if i == numChunks-1 {
198198
chunkSize = fileInfo.Size() - offset
199-
inputFile.Data = make([]byte, chunkSize)
199+
payload.Data = make([]byte, chunkSize)
200200
}
201-
_, err := file.ReadAt(inputFile.Data, offset)
201+
_, err := file.ReadAt(payload.Data, offset)
202202
if err != nil && err != io.EOF {
203203
return nil, err
204204
}
205-
params[paramName] = inputFile
205+
params[paramName] = payload
206206
if uploadId != "" && uploadId != "unique()" {
207207
headers["x-appwrite-id"] = uploadId
208208
}
@@ -248,18 +248,19 @@ func (client *Client) Call(method string, path string, headers map[string]interf
248248
isGet := strings.ToUpper(method) == "GET"
249249
isPost := strings.ToUpper(method) == "POST"
250250
isJsonRequest := headers["content-type"] == "application/json"
251-
isFileUpload := isFileUpload(headers)
251+
isMultipart := isMultipart(headers)
252252

253253
var req *http.Request
254254
var err error
255-
if isFileUpload {
255+
if isMultipart {
256+
headers["accept"] = "multipart/form-data"
256257
if !isPost {
257258
return nil, errors.New("fileupload needs POST Request")
258259
}
259260
var body bytes.Buffer
260261
writer := multipart.NewWriter(&body)
261262
for key, val := range params {
262-
if file, ok := val.(file.InputFile); ok {
263+
if file, ok := val.(*payload.Payload); ok {
263264
fileName := file.Name
264265
fileData := file.Data
265266
fw, err := writer.CreateFormFile(key, fileName)

templates/go/docs/example.md.twig

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,26 @@ package main
88

99
import (
1010
"fmt"
11-
"github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}/client"
12-
"github.com/{{ sdk.gitUserName|url_encode }}/{{ sdk.gitRepoName|url_encode }}/{{ service.name | caseLower }}"
13-
{% if requireFilesPkg %}
14-
"github.com/{{sdk.gitUserName}}/sdk-for-go/file"
11+
"github.com/{{sdk.gitUserName}}/sdk-for-go/appwrite"
12+
{% if requireFilesPkg or method.name | caseLower == "createexecution" %}
13+
"github.com/{{sdk.gitUserName}}/sdk-for-go/payload"
1514
{% endif %}
1615
)
1716

1817
func main() {
19-
client := client.NewClient()
20-
18+
client := appwrite.NewClient(
2119
{% if method.auth|length > 0 %}
22-
client.SetEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
20+
appwrite.WithEndpoint("https://cloud.appwrite.io/v1"), // Your API Endpoint
2321
{% for node in method.auth %}
2422
{% for key,header in node|keys %}
25-
client.Set{{header}}("{{node[header]['x-appwrite']['demo'] | raw }}") // {{node[header].description}}
23+
appwrite.With{{header}}("{{node[header]['x-{{ spec.title | caseLower }}']['demo']}}"), // {{node[header].description}}
2624
{% endfor %}
2725
{% endfor %}
26+
)
2827

2928
{% endif %}
30-
service := {{ service.name | caseLower }}.New{{ service.name | caseUcfirst }}(client)
31-
response, error := service.{{ method.name | caseUcfirst }}(
29+
{{service.name}} := appwrite.New{{ service.name | caseUcfirst }}(client)
30+
response, error := {{service.name}}.{{ method.name | caseUcfirst }}(
3231
{% for parameter in method.parameters.all %}
3332
{% if parameter.required %}
3433
{{ parameter | paramExample }},

0 commit comments

Comments
 (0)