feat(12.2-02): add the fileupload field with deferred uploads committed on save

- type: fileupload compiles the D-08 keys and binds to the model's attach.Relation at boot
- X-Session-Key (cabana.SessionKeyHeader) carries the form session key; RecordInput.SessionKey
- GET and POST .../{id}/files/{field}: list with pending uploads, multipart upload into attach.Store
- the create and update save attaches the session's pending files in its transaction
- swagger2openapi folds formData parameters into a multipart requestBody
- admin OpenAPI, TS types, conformance cases, README and forms docs
This commit is contained in:
Jakub Zych
2026-10-02 18:04:34 +02:00
parent edda803dc1
commit 044e0450ef
22 changed files with 2183 additions and 21 deletions

View File

@@ -201,3 +201,36 @@ func TestMalformedShapesPassThrough(t *testing.T) {
t.Fatalf("foreign ref rewritten: %v", got)
}
}
func TestConvertFormData(t *testing.T) {
op := decode(t, `{
"consumes": ["multipart/form-data"],
"parameters": [
{"name": "id", "in": "path", "required": true, "type": "integer"},
{"name": "file_data", "in": "formData", "required": true, "type": "file", "description": "The file"},
{"name": "note", "in": "formData", "type": "string"}
],
"responses": {"201": {"description": "Created"}}
}`)
out := convertOperation(op, nil, nil)
params := out["parameters"].([]any)
if len(params) != 1 || params[0].(map[string]any)["name"] != "id" {
t.Fatalf("parameters = %#v", params)
}
body := out["requestBody"].(map[string]any)
if body["required"] != true {
t.Fatalf("request body = %#v", body)
}
schema := body["content"].(map[string]any)["multipart/form-data"].(map[string]any)["schema"].(map[string]any)
want := map[string]any{
"type": "object",
"properties": map[string]any{
"file_data": map[string]any{"type": "string", "format": "binary", "description": "The file"},
"note": map[string]any{"type": "string"},
},
"required": []any{"file_data"},
}
if !reflect.DeepEqual(schema, want) {
t.Fatalf("form schema = %#v", schema)
}
}