Skip to content

Commit

Permalink
Merge pull request #278 from go-generalize/tsuzu/multipart-file
Browse files Browse the repository at this point in the history
Uploading in multipart/form-data
  • Loading branch information
54m authored Feb 24, 2022
2 parents df7a346 + d35d8e2 commit d0f37ab
Show file tree
Hide file tree
Showing 57 changed files with 2,457 additions and 2,920 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/go-generalize/api_gen/v2
go 1.17

require (
github.com/go-generalize/go2go v0.0.0-20210311170338-9701de42e3ad
github.com/go-generalize/go2go v0.2.1
github.com/go-generalize/go2ts v1.6.0
github.com/go-utils/count v0.0.1
github.com/go-utils/gopackages v0.0.0-20210218102646-e7d1f0008968
Expand All @@ -21,9 +21,11 @@ require (
)

require (
github.com/fatih/structtag v1.2.0
github.com/go-generalize/go-dartfmt v0.1.1
github.com/go-generalize/go-easyparser v0.2.0
github.com/go-generalize/go2dart v0.5.1
github.com/go-utils/echo-multipart-binder v0.2.1
github.com/swaggo/swag v1.8.0
)

Expand All @@ -38,6 +40,7 @@ require (
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/labstack/gommon v0.3.1 // indirect
Expand All @@ -48,5 +51,6 @@ require (
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
14 changes: 12 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-generalize/go-dartfmt v0.1.1 h1:2W+CD7qGCNE/0JLtm394RJBBi/jhtXUcJ+G0v0Bcx88=
Expand All @@ -128,8 +130,12 @@ github.com/go-generalize/go-easyparser v0.2.0 h1:BAkuf9qkgIX1rm2nMVT3HiXO787Tnja
github.com/go-generalize/go-easyparser v0.2.0/go.mod h1:I+Ifnjzw7UNzxkENknQQY9yB2gsz9RekzAbKLYq/Dzw=
github.com/go-generalize/go2dart v0.5.1 h1:eeSjP7GMArcPHZ+sMYQxU79MxjHDqaQvLcNHdV0erFI=
github.com/go-generalize/go2dart v0.5.1/go.mod h1:peqHvoBnlWmpJPoYaIyGBditWFaNZ6jR0ywbIRNdY8o=
github.com/go-generalize/go2go v0.0.0-20210311170338-9701de42e3ad h1:9oYY/ZcAf3VVRhxaxolqoTVprrx9A7N8INWAfWCRwBc=
github.com/go-generalize/go2go v0.0.0-20210311170338-9701de42e3ad/go.mod h1:YCmZm0ss4r71rT/Q6AgK4vre4tSFfJ1W/eDHWsmnLCc=
github.com/go-generalize/go2go v0.1.0 h1:BqCWA5ifMkEsvKTxcMuiSfu5zuMPg1j2eQVOhz2ievY=
github.com/go-generalize/go2go v0.1.0/go.mod h1:lYuZa6CIQf8p4uNaojvN9zktOdkjU6+REKwIflYVLEM=
github.com/go-generalize/go2go v0.2.0 h1:FUWO5aNhLHG7kzLQjGgS2MCmHKnN1/o6NfZ1R0Sz2ug=
github.com/go-generalize/go2go v0.2.0/go.mod h1:lYuZa6CIQf8p4uNaojvN9zktOdkjU6+REKwIflYVLEM=
github.com/go-generalize/go2go v0.2.1 h1:YLUiBx8W9WgSeYDJnuF79Hx0dkMZu1VZygeyasCbyvk=
github.com/go-generalize/go2go v0.2.1/go.mod h1:lYuZa6CIQf8p4uNaojvN9zktOdkjU6+REKwIflYVLEM=
github.com/go-generalize/go2ts v1.0.5-0.20210311100632-95c274e947f1/go.mod h1:075q+zcpc6IjwYy9WpJpY+M9ssbEA6uccjzWwr59t3M=
github.com/go-generalize/go2ts v1.6.0 h1:a+ysTs5xVX3MT0nrAk8sEO8pGBgCPOF2LywW92JmQIY=
github.com/go-generalize/go2ts v1.6.0/go.mod h1:TjBIzrW+K1EZCbLkh32OL1ramp54lFpVImiK6jmZvBs=
Expand All @@ -155,11 +161,14 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-utils/count v0.0.1 h1:mt55LmS+NgZjldyZx5IQqpXcmbV1Zzsh+EtxSNW9YeM=
github.com/go-utils/count v0.0.1/go.mod h1:jTOK09i9ez7PdsTjscdN+iDrXGq5YkDTDYuiVy7hRZw=
github.com/go-utils/echo-multipart-binder v0.2.1 h1:LFBdsPG6eTuuO8DEUT2LZBz2Tb3wEk+1+ZmiE85Tcs8=
github.com/go-utils/echo-multipart-binder v0.2.1/go.mod h1:I9RUOS+h5TPFy96MURphPO1Yxe7nSUz0v3i0sanPU0U=
github.com/go-utils/gopackages v0.0.0-20210218102646-e7d1f0008968 h1:sHm78LgJXVdlQPfpmVq4tLmssocz+tda7NJgdH6LJFY=
github.com/go-utils/gopackages v0.0.0-20210218102646-e7d1f0008968/go.mod h1:QPVW4BzGDze3cEu7TbM7Gw9ppfYTj4NDgBW0JwNZE1g=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -635,6 +644,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
105 changes: 100 additions & 5 deletions pkg/client/dart/classes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"

"github.com/fatih/structtag"
"github.com/go-generalize/api_gen/v2/pkg/parser"
types "github.com/go-generalize/go-easyparser/types"
util "github.com/go-generalize/go-easyparser/util"
Expand All @@ -15,7 +16,17 @@ import (

// GenerateTypes generates request/response types in Dart
func (g *generator) GenerateTypes(fn func(relPath, code string) error) error {
err := g.generateTypes(g.root, fn)
code, err := go2dartgenerator.NewGenerator(nil, nil).Generate()

if err != nil {
return xerrors.Errorf("failed to generate common JsonConverter: %w", err)
}

if err := fn("common.dart", code); err != nil {
return xerrors.Errorf("failed to save common.dart: %w", err)
}

err = g.generateTypes(g.root, fn)

if err != nil {
return xerrors.Errorf("failed to generate request/response types: %w", err)
Expand All @@ -24,16 +35,27 @@ func (g *generator) GenerateTypes(fn func(relPath, code string) error) error {
return nil
}

const fileHeaderName = "mime/multipart.FileHeader"

func (g *generator) generateTypes(gr *parser.Group, fn func(relPath, code string) error) error {
for _, child := range gr.Children {
if err := g.generateTypes(child, fn); err != nil {
return xerrors.Errorf("an error occurred in %s: %w", gr.Dir, err)
}
}

replaceFileHeaderAll(gr.ParsedTypes)

gen := go2dartgenerator.NewGenerator(gr.ParsedTypes, nil)

gen.ExternalImporter = func(o *types.Object) *go2dartgenerator.ExternalImporter {
if o.Name == fileHeaderName {
return &go2dartgenerator.ExternalImporter{
Path: "package:http/http.dart",
Name: "MultipartFile",
}
}

rel, err := filepath.Rel(g.root.Dir, o.Position.Filename)

if err != nil {
Expand Down Expand Up @@ -62,16 +84,24 @@ func (g *generator) generateTypes(gr *parser.Group, fn func(relPath, code string
}
}

relative := gr.GetFullPath(string(filepath.Separator), func(rawPath, path, placeholder string) string {
return rawPath
})

reversedRelative, err := filepath.Rel(filepath.Join(".", relative), ".")

if err != nil {
return xerrors.Errorf("failed to get reversed relative path: %w", err)
}

gen.ExternalCommonConverterPath = filepath.Join(reversedRelative, "../common.dart")

code, err := gen.Generate()

if err != nil {
return xerrors.Errorf("failed to generate: %w", err)
}

relative := gr.GetFullPath(string(filepath.Separator), func(rawPath, path, placeholder string) string {
return rawPath
})

code = fmt.Sprintf(headerComment, g.AppVersion) + code

p := filepath.Join("classes", relative, "types.dart")
Expand All @@ -81,3 +111,68 @@ func (g *generator) generateTypes(gr *parser.Group, fn func(relPath, code string

return nil
}

func replaceFileHeaderAll(t map[string]types.Type) {
for _, v := range t {
v, ok := v.(*types.Object)

if !ok {
continue
}

replaceFileHeader(v)
}
}

func replaceFileHeader(obj *types.Object) {
for k, v := range obj.Entries {
if o, ok := v.Type.(*types.Object); ok {
replaceFileHeader(o)

continue
}

tags, err := structtag.Parse(v.RawTag)

if err != nil {
continue
}

jsonTag, err := tags.Get("json")

if err != nil {
jsonTag = &structtag.Tag{
Key: "json",
Name: "-",
}
}
// nolint:errcheck
tags.Set(jsonTag)

t, err := parser.ValidateMultipartUploadType(v.Type, v.RawTag)

if err != nil || t == parser.UploadNone {
continue
}

if t == parser.UploadSingleFile {
v.Type = &types.Nullable{
Inner: &types.Object{
Name: fileHeaderName,
},
}
} else {
v.Type = &types.Nullable{
Inner: &types.Array{
Inner: &types.Object{
Name: fileHeaderName,
},
},
}
}

v.RawTag = tags.String()

obj.Entries[k] = v
}
}
19 changes: 19 additions & 0 deletions pkg/client/dart/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (g *generator) generateEndpoint(ep *parser.Endpoint, packageAlias string) *
Method: strings.ToUpper(string(ep.Method)),
HasFields: len(ep.ResponsePayload.Fields.List) != 0,
Name: strings.ToLower(string(ep.Method)) + strcase.ToCamel(ep.Path),
Multipart: ep.UseMultipartUpload,
}

endpoint.Endpoint = ep.GetFullPath("/", func(rawPath, path, placeholder string) string {
Expand All @@ -66,6 +67,24 @@ func (g *generator) generateEndpoint(ep *parser.Endpoint, packageAlias string) *
return path
})

res, _ := parser.GetFileFields(ep.RequestGo2tsPayload)
fileFields := make([]fileField, 0, len(res))

for _, v := range res {
isArray := v.Type == parser.UploadMultipleFiles
fileFields = append(fileFields, fileField{
MultipartField: v.FormTag,
StructField: go2dartgenerator.ReplaceFieldName(v.Value.RawName),
IsArray: isArray,
})

if isArray {
g.ImportHTTPParser = true
}
}

endpoint.FileFields = fileFields

urlParams := make(map[string]string)
for key, field := range ep.RequestGo2tsPayload.Entries {
param := field.RawName
Expand Down
63 changes: 60 additions & 3 deletions pkg/client/dart/templates/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// generated version: {{ .AppVersion }}
import 'dart:convert';
import 'package:http/http.dart' as http;
{{- if .ImportHTTPParser }}
import 'package:http_parser/http_parser.dart' as http_parser;
{{- end }}
{{- range $index, $elem := .Imports }}
import '{{ $elem.Path }}.dart' as {{ $elem.Alias }};
{{- end }}
Expand Down Expand Up @@ -81,6 +84,32 @@ class {{ $elem.Name }} {
Uri.parse(url),
headers: headers,
);
{{- else if $method.Multipart }}
final request = http.MultipartRequest('{{ $method.Method }}', Uri.parse(url))
..headers = headers
..files.add(http.MultipartFile.fromString(
'x-multipart-json-binder-request-json', jsonEncode(getRequestObject(param.toJson(), excludeParams, false)),
filename: 'x-multipart-json-binder-request-json', contentType: http_parser.MediaType.parse('application/json')
));
{{- range $index, $field := $method.FileFields }}
{{ if $field.IsArray }}
param.{{ $field.StructField }}.forEach((http.MultipartFile file) {
request.files.add(http.MultipartFile(
'{{ $field.MultipartField }}', file.finalize(), file.length,
filename: file.filename ?? 'untitled', contentType: file.contentType
));
});
{{ else }}
if (param.{{ $field.StructField }} != null) {
final file = param.{{ $field.StructField }}!;
request.files.add(http.MultipartFile(
'{{ $field.MultipartField }}', file.finalize(), file.length,
filename: file.filename ?? 'untitled', contentType: file.contentType
));
}
{{- end }}
{{- end }}
final resp = await client.send(request);
{{- else }}
final resp = await client.{{ toLower $method.Method }}(
Uri.parse(url),
Expand All @@ -94,7 +123,7 @@ class {{ $elem.Name }} {
}

{{- if eq .HasFields true }}
final res = {{ $method.ResponseType }}.fromJson(jsonDecode(resp.body));
final res = {{ $method.ResponseType }}.fromJson(jsonDecode({{if .Multipart}}await resp.stream.bytesToString(){{else}}resp.body{{end}}));
{{- else }}
final res = {{ $method.ResponseType }}();
{{- end }}
Expand Down Expand Up @@ -194,6 +223,34 @@ class APIClient {
Uri.parse(url),
headers: headers,
);
{{- else if $method.Multipart }}
final request = http.MultipartRequest('{{ $method.Method }}', Uri.parse(url))
..headers.addAll(headers)
..files.add(http.MultipartFile.fromString(
'x-multipart-json-binder-request-json', jsonEncode(getRequestObject(param.toJson(), excludeParams)),
filename: 'x-multipart-json-binder-request-json', contentType: http_parser.MediaType.parse('application/json')
));
{{- range $index, $field := $method.FileFields }}
{{ if $field.IsArray }}
if (param.{{ $field.StructField }} != null) {
param.{{ $field.StructField }}!.forEach((http.MultipartFile file) {
request.files.add(http.MultipartFile(
'{{ $field.MultipartField }}', file.finalize(), file.length,
filename: file.filename ?? 'untitled', contentType: file.contentType
));
});
}
{{ else }}
if (param.{{ $field.StructField }} != null) {
final file = param.{{ $field.StructField }}!;
request.files.add(http.MultipartFile(
'{{ $field.MultipartField }}', file.finalize(), file.length,
filename: file.filename ?? 'untitled', contentType: file.contentType
));
}
{{- end }}
{{- end }}
final resp = await client.send(request);
{{- else }}
final resp = await client.{{ toLower $method.Method }}(
Uri.parse(url),
Expand All @@ -207,7 +264,7 @@ class APIClient {
}

{{- if eq .HasFields true }}
final res = {{ $method.ResponseType }}.fromJson(jsonDecode(resp.body));
final res = {{ $method.ResponseType }}.fromJson(jsonDecode({{if .Multipart}}await resp.stream.bytesToString(){{else}}resp.body{{end}}));
{{- else }}
final res = {{ $method.ResponseType }}();
{{- end }}
Expand All @@ -218,7 +275,7 @@ class APIClient {
}

class ApiError extends Error {
final http.Response response;
final http.BaseResponse response;

ApiError(this.response);

Expand Down
Loading

0 comments on commit d0f37ab

Please sign in to comment.