@@ -3,11 +3,15 @@ package generator
33import (
44 "bytes"
55 "encoding/json"
6+ "fmt"
67 "strings"
78
89 "github.com/getkin/kin-openapi/openapi3"
10+ "github.com/stretchr/objx"
11+ oapiv1 "github.com/technicallyjosh/protoc-gen-openapi/api/oapi/v1"
912 "github.com/technicallyjosh/protoc-gen-openapi/internal/generator/util"
1013 "google.golang.org/protobuf/compiler/protogen"
14+ "google.golang.org/protobuf/proto"
1115 "gopkg.in/yaml.v3"
1216)
1317
@@ -78,20 +82,88 @@ func (g *Generator) Run() error {
7882 }
7983 }
8084
85+ fileBytes := fileBuffer .Bytes ()
86+
8187 outFile := g .plugin .NewGeneratedFile (filename , "" )
82- _ , err = outFile .Write (fileBuffer .Bytes ())
8388
89+ patchedBytes , err := g .patchEmptySchemas (fileBytes )
90+ if err != nil {
91+ return err
92+ }
93+
94+ _ , err = outFile .Write (patchedBytes )
8495 return err
8596}
8697
98+ // patchEmptySchemas finds any schemas that are empty and updates them to have
99+ // an empty `Properties` node.
100+ func (g * Generator ) patchEmptySchemas (fileBytes []byte ) ([]byte , error ) {
101+ type M = map [string ]any
102+ data := make (M )
103+
104+ useJSON := * g .config .JSONOutput
105+
106+ var err error
107+ if useJSON {
108+ err = json .Unmarshal (fileBytes , & data )
109+ } else {
110+ err = yaml .Unmarshal (fileBytes , & data )
111+ }
112+ if err != nil {
113+ return nil , err
114+ }
115+
116+ jsonBytes , err := json .Marshal (data )
117+ if err != nil {
118+ return nil , err
119+ }
120+
121+ m , err := objx .FromJSON (string (jsonBytes ))
122+ if err != nil {
123+ return nil , err
124+ }
125+
126+ for pathKey := range m .Get ("paths" ).ObjxMap () {
127+ pathPath := "paths." + pathKey
128+
129+ for methodKey := range m .Get (pathPath ).ObjxMap () {
130+ methodPath := pathPath + "." + methodKey
131+
132+ schemaKey := fmt .Sprintf ("%s.requestBody.content.application/json.schema" , methodPath )
133+ schema := m .Get (schemaKey )
134+
135+ if schema .IsObjxMap () && len (schema .ObjxMap ()) == 0 {
136+ schema .ObjxMap ().Set ("properties" , M {})
137+ }
138+
139+ for resKey := range m .Get (methodPath + ".responses" ).ObjxMap () {
140+ schemaKey := fmt .Sprintf ("%s.responses.%s.content.application/json.schema" , methodPath , resKey )
141+ schema := m .Get (schemaKey )
142+ if schema .IsObjxMap () && len (schema .ObjxMap ()) == 0 {
143+ schema .ObjxMap ().Set ("properties" , M {})
144+ }
145+ }
146+ }
147+ }
148+
149+ if useJSON {
150+ return json .Marshal (m )
151+ }
152+
153+ buffer := bytes.Buffer {}
154+ encoder := yaml .NewEncoder (& buffer )
155+ encoder .SetIndent (2 )
156+
157+ err = encoder .Encode (m )
158+ return buffer .Bytes (), err
159+ }
160+
87161// buildDocument builds out the base of the OAPI document with some defaults.
88162func (g * Generator ) buildDocument () (* openapi3.T , error ) {
89163 doc := & openapi3.T {
90- ExtensionProps : openapi3.ExtensionProps {
91- Extensions : make (map [string ]any ),
92- },
93- OpenAPI : "3.0.3" ,
94- Components : openapi3.Components {
164+ Extensions : make (map [string ]any ),
165+ OpenAPI : "3.1.0" ,
166+ Components : & openapi3.Components {
95167 SecuritySchemes : make (openapi3.SecuritySchemes ),
96168 Schemas : make (openapi3.Schemas ),
97169 RequestBodies : make (openapi3.RequestBodies ),
@@ -130,6 +202,12 @@ func (g *Generator) buildDocument() (*openapi3.T, error) {
130202 }
131203
132204 for _ , file := range files {
205+ // Add servers even if there isn't a service. (File-based)
206+ err = addFileServersToDoc (doc , file )
207+ if err != nil {
208+ return nil , err
209+ }
210+
133211 err = g .addPathsToDoc (doc , file .Services )
134212 if err != nil {
135213 return nil , err
@@ -142,6 +220,23 @@ func (g *Generator) buildDocument() (*openapi3.T, error) {
142220 return doc , nil
143221}
144222
223+ func addFileServersToDoc (doc * openapi3.T , file * protogen.File ) error {
224+ extFile := proto .GetExtension (file .Desc .Options (), oapiv1 .E_File )
225+ if extFile != nil && extFile != oapiv1 .E_File .InterfaceOf (oapiv1 .E_File .Zero ()) {
226+ fileOptions := extFile .(* oapiv1.FileOptions )
227+
228+ if fileOptions .Host != "" {
229+ server , err := NewServer (fileOptions .Host )
230+ if err != nil {
231+ return err
232+ }
233+ doc .Servers = append (doc .Servers , server )
234+ }
235+ }
236+
237+ return nil
238+ }
239+
145240func filterFiles (allFiles []* protogen.File , ignored []string ) []* protogen.File {
146241 files := make ([]* protogen.File , 0 )
147242
0 commit comments