Skip to content

Commit e2df318

Browse files
fgschwanMilan Lenčo
and
Milan Lenčo
authored
feat: Added REST API for retrieving JSON schema for VPP-Agent configuration (ligato#1784)
* feat: added external proto to jsonschema converter into ligato code Signed-off-by: Filip Gschwandtner <[email protected]> * feat: added REST API for retrieving JSON schema of VPP-Agent configuration Signed-off-by: Filip Gschwandtner <[email protected]> * refactor: removed unusable unit tests (need to remove dependency on external protoc program and other tweaks to fully integrate into ligato) Signed-off-by: Filip Gschwandtner <[email protected]> * refactor: replaced dependency reference to original jsonschema library with its dependencies Signed-off-by: Filip Gschwandtner <[email protected]> * docs: added proper README documentation and licensing for included tool Signed-off-by: Filip Gschwandtner <[email protected]> * fix: relaxed enum type safety to fix issues with 3rd party json example createor (enum is still safe enough due to value enumeration of all possible values) Signed-off-by: Filip Gschwandtner <[email protected]> * refactor: removed jsonschema files that seemed like templates for generation but serve no purpose at all (probably generated by test or something and commited into repo) Signed-off-by: Filip Gschwandtner <[email protected]> * doc: updated initial changes in README.md for last 2 commits Signed-off-by: Filip Gschwandtner <[email protected]> * fix: removed duplicite and unnecessary http status print into http response body and removed unused code Signed-off-by: Filip Gschwandtner <[email protected]> * feat: added URL parameter for JSON schema REST API to be able to change what kind of field names are used (proto names/json names/both) Signed-off-by: Filip Gschwandtner <[email protected]> Co-authored-by: Milan Lenčo <[email protected]>
1 parent d7cf8be commit e2df318

13 files changed

+1515
-8
lines changed

LICENSE

+6
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,9 @@
199199
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200200
See the License for the specific language governing permissions and
201201
limitations under the License.
202+
203+
204+
Licenses for 3rd party libraries included in code:
205+
206+
1. library protoc-gen-jsonschema (https://github.com/chrusty/protoc-gen-jsonschema) -
207+
License: Apache License Version 2.0, January 2004 (full license included in plugins/restapi/jsonschema/LICENSE)

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ If you are interested in contributing, please see the [contribution guidelines][
116116

117117
[![GitHub license](https://img.shields.io/badge/license-Apache%20license%202.0-blue.svg)](https://github.com/ligato/vpp-agent/blob/master/LICENSE)
118118

119+
## Modified 3rd party tools included
120+
121+
- [protoc-gen-jsonschema][tool-included-jsonchema] ([code location in this repository][local-place-for-jsonchema])
122+
119123
[agentctl]: cmd/agentctl
120124
[cn-infra]: https://github.com/ligato/cn-infra
121125
[contiv-vpp]: https://github.com/contiv/vpp
@@ -131,3 +135,5 @@ If you are interested in contributing, please see the [contribution guidelines][
131135
[vpp]: https://fd.io/vppproject/vpptech/
132136
[vpp-agent]: https://hub.docker.com/r/ligato/vpp-agent
133137
[vpp-agent-arm64]: https://hub.docker.com/r/ligato/vpp-agent-arm64
138+
[tool-included-jsonchema]: https://github.com/chrusty/protoc-gen-jsonschema/tree/de75f1b59c4e0f5d5edf7be2a18d1c8e4d81b17a
139+
[local-place-for-jsonchema]: plugins/restapi/jsonschema

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.13
55
require (
66
git.fd.io/govpp.git v0.3.6-0.20200907135408-e517439567ad
77
github.com/Shopify/sarama v1.20.1 // indirect
8+
github.com/alecthomas/jsonschema v0.0.0-20200217214135-7152f22193c9
89
github.com/alicebob/miniredis v2.5.0+incompatible // indirect
910
github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2
1011
github.com/coreos/bbolt v1.3.3 // indirect
@@ -26,6 +27,7 @@ require (
2627
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
2728
github.com/grpc-ecosystem/grpc-gateway v1.11.3 // indirect
2829
github.com/hashicorp/go-uuid v1.0.1 // indirect
30+
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0
2931
github.com/jhump/protoreflect v1.7.0
3032
github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe
3133
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
@@ -44,6 +46,9 @@ require (
4446
github.com/unrolled/render v0.0.0-20180914162206-b9786414de4d
4547
github.com/vishvananda/netlink v0.0.0-20180910184128-56b1bd27a9a3
4648
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc
49+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
50+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
51+
github.com/xeipuuv/gojsonschema v1.1.0
4752
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 // indirect
4853
go.ligato.io/cn-infra/v2 v2.5.0-alpha.0.20200313154441-b0d4c1b11c73
4954
go.uber.org/multierr v1.2.0 // indirect

go.sum

+17
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ github.com/Shopify/sarama v1.20.1/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
2222
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
2323
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
2424
github.com/Songmu/prompter v0.0.0-20150725163906-b5721e8d5566/go.mod h1:fNhSFBGC+sg+dZ7AqDHgq+xYiom23TeTESzUbO7PIrE=
25+
github.com/alecthomas/jsonschema v0.0.0-20200217214135-7152f22193c9 h1:h+KAZEUnNceFhqyH46BgwH4lk8m6pdR/3x3h7IPn7VA=
26+
github.com/alecthomas/jsonschema v0.0.0-20200217214135-7152f22193c9/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
2527
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
2628
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
2729
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -51,6 +53,8 @@ github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJ
5153
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
5254
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
5355
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
56+
github.com/chrusty/protoc-gen-jsonschema v0.0.0-20201201182816-de75f1b59c4e h1:VEDA+FrTIUnlSpMlo2i1e0L1hP45vek2ED+blYdOrxg=
57+
github.com/chrusty/protoc-gen-jsonschema v0.0.0-20201201182816-de75f1b59c4e/go.mod h1:qYuJI3Nz/kjHcigPikCSSeh+pRGcCiT1U6qzWGEmaJ4=
5458
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
5559
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
5660
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -266,6 +270,8 @@ github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8k
266270
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw=
267271
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
268272
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
273+
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
274+
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
269275
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
270276
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
271277
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -281,6 +287,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
281287
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
282288
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
283289
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
290+
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
284291
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
285292
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
286293
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -447,6 +454,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
447454
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
448455
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
449456
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
457+
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
450458
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
451459
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
452460
github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8=
@@ -464,6 +472,14 @@ github.com/vishvananda/netlink v0.0.0-20180910184128-56b1bd27a9a3/go.mod h1:+SR5
464472
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
465473
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
466474
github.com/willfaught/gockle v0.0.0-20160623235217-4f254e1e0f0a/go.mod h1:NLcF+3nDpXVIZatjn5Z97gKzFFVU7TzgbAcs8G7/Jrs=
475+
github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b h1:6cLsL+2FW6dRAdl5iMtHgRogVCff0QpRi9653YmdcJA=
476+
github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
477+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
478+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
479+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
480+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
481+
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
482+
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
467483
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
468484
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
469485
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
@@ -560,6 +576,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
560576
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
561577
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
562578
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
579+
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
563580
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
564581
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
565582
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

plugins/restapi/handlers.go

+155
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,41 @@
1717
package restapi
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"fmt"
2223
"net/http"
2324
"runtime"
25+
"strings"
2426

2527
"github.com/go-errors/errors"
28+
"github.com/golang/protobuf/proto"
29+
protoc_plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
2630
"github.com/unrolled/render"
2731

32+
"go.ligato.io/cn-infra/v2/logging/logrus"
33+
"go.ligato.io/vpp-agent/v3/client"
2834
"go.ligato.io/vpp-agent/v3/cmd/agentctl/api/types"
2935
"go.ligato.io/vpp-agent/v3/pkg/version"
3036
"go.ligato.io/vpp-agent/v3/plugins/configurator"
37+
"go.ligato.io/vpp-agent/v3/plugins/restapi/jsonschema/converter"
3138
"go.ligato.io/vpp-agent/v3/plugins/restapi/resturl"
3239
interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
40+
"google.golang.org/protobuf/reflect/protodesc"
41+
"google.golang.org/protobuf/reflect/protoreflect"
42+
"google.golang.org/protobuf/types/descriptorpb"
43+
)
44+
45+
const (
46+
// URLFieldNamingParamName is URL parameter name for JSON schema http handler's setting
47+
// to output field names using proto/json/both names for fields
48+
URLFieldNamingParamName = "fieldnames"
49+
// OnlyProtoFieldNames is URL parameter value for JSON schema http handler to use only proto names as field names
50+
OnlyProtoFieldNames = "onlyproto"
51+
// OnlyJSONFieldNames is URL parameter value for JSON schema http handler to use only JSON names as field names
52+
OnlyJSONFieldNames = "onlyjson"
53+
54+
internalErrorLogPrefix = "500 Internal server error: "
3355
)
3456

3557
var (
@@ -40,6 +62,7 @@ var (
4062

4163
func (p *Plugin) registerInfoHandlers() {
4264
p.HTTPHandlers.RegisterHTTPHandler(resturl.Version, p.versionHandler, GET)
65+
p.HTTPHandlers.RegisterHTTPHandler(resturl.JSONSchema, p.jsonSchemaHandler, GET)
4366
}
4467

4568
// Registers ABF REST handler
@@ -315,6 +338,138 @@ func (p *Plugin) registerHTTPHandler(key, method string, f func() (interface{},
315338
p.HTTPHandlers.RegisterHTTPHandler(key, handlerFunc, method)
316339
}
317340

341+
// jsonSchemaHandler returns JSON schema of VPP-Agent configuration.
342+
// This handler also accepts URL query parameters changing the exported field names of proto messages. By default,
343+
// proto message fields are exported twice in JSON scheme. Once with proto name and once with JSON name. This should
344+
// allow to use any of the 2 forms in JSON/YAML configuration when used JSON schema for validation. However,
345+
// this behaviour can be modified by URLFieldNamingParamName URL query parameter, that force to export only
346+
// proto named fields (OnlyProtoFieldNames URL query parameter value) or JSON named fields (OnlyJSONFieldNames
347+
// URL query parameter value).
348+
func (p *Plugin) jsonSchemaHandler(formatter *render.Render) http.HandlerFunc {
349+
return func(w http.ResponseWriter, req *http.Request) {
350+
// create FileDescriptorProto for dynamic Config holding all VPP-Agent configuration
351+
knownModels, err := client.LocalClient.KnownModels("config") // locally registered models
352+
if err != nil {
353+
errMsg := fmt.Sprintf("can't get registered models: %v\n", err)
354+
p.Log.Error(internalErrorLogPrefix + errMsg)
355+
p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg))
356+
return
357+
}
358+
config, err := client.NewDynamicConfig(knownModels)
359+
if err != nil {
360+
errMsg := fmt.Sprintf("can't create dynamic config due to: %v\n", err)
361+
p.Log.Error(internalErrorLogPrefix + errMsg)
362+
p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg))
363+
return
364+
}
365+
dynConfigFileDescProto := protodesc.ToFileDescriptorProto(config.ProtoReflect().Descriptor().ParentFile())
366+
367+
// create list of all FileDescriptorProtos (imports should be before converted proto file -> dynConfig is last)
368+
fileDescriptorProtos := allFileDescriptorProtos(knownModels)
369+
fileDescriptorProtos = append(fileDescriptorProtos, dynConfigFileDescProto)
370+
371+
// creating input for protoc's plugin (code extracted in plugins/restapi/jsonschema) that can convert
372+
// FileDescriptorProtos to JSONSchema
373+
params := []string{
374+
"messages=[Dynamic_config]", // targeting only the main config message (proto file has also other messages)
375+
"disallow_additional_properties", // additional unknown json fields makes configuration applying fail
376+
}
377+
fieldNamesConverterParam := "proto_and_json_fieldnames" // create proto and json named fields by default
378+
if fieldNames, found := req.URL.Query()[URLFieldNamingParamName]; found && len(fieldNames) > 0 {
379+
// converting REST API request params to 3rd party tool params
380+
switch fieldNames[0] {
381+
case OnlyProtoFieldNames:
382+
fieldNamesConverterParam = ""
383+
case OnlyJSONFieldNames:
384+
fieldNamesConverterParam = "json_fieldnames"
385+
}
386+
}
387+
if fieldNamesConverterParam != "" {
388+
params = append(params, fieldNamesConverterParam)
389+
}
390+
paramsStr := strings.Join(params, ",")
391+
cgReq := &protoc_plugin.CodeGeneratorRequest{
392+
ProtoFile: fileDescriptorProtos,
393+
FileToGenerate: []string{dynConfigFileDescProto.GetName()},
394+
Parameter: &paramsStr,
395+
CompilerVersion: nil, // compiler version is not need in this protoc plugin
396+
}
397+
cgReqMarshalled, err := proto.Marshal(cgReq)
398+
if err != nil {
399+
errMsg := fmt.Sprintf("can't proto marshal CodeGeneratorRequest: %v\n", err)
400+
p.Log.Error(internalErrorLogPrefix + errMsg)
401+
p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg))
402+
return
403+
}
404+
405+
// use JSON schema converter and handle error cases
406+
p.Log.Debug("Processing code generator request")
407+
protoConverter := converter.New(logrus.DefaultLogger().StandardLogger())
408+
res, err := protoConverter.ConvertFrom(bytes.NewReader(cgReqMarshalled))
409+
if err != nil {
410+
if res == nil {
411+
errMsg := fmt.Sprintf("failed to read registered model configuration input: %v\n", err)
412+
p.Log.Error(internalErrorLogPrefix + errMsg)
413+
p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg))
414+
return
415+
}
416+
errMsg := fmt.Sprintf("failed generate JSON schema: %v (%v)\n", res.Error, err)
417+
p.Log.Error(internalErrorLogPrefix + errMsg)
418+
p.logError(formatter.JSON(w, http.StatusInternalServerError, errMsg))
419+
return
420+
}
421+
422+
// extract json schema
423+
// (protoc_plugin.CodeGeneratorResponse could have cut the file content into multiple pieces
424+
// for performance optimization (due to godoc), but we know that all pieces are only one file
425+
// due to requesting one file -> join all content together)
426+
var sb strings.Builder
427+
for _, file := range res.File {
428+
sb.WriteString(file.GetContent())
429+
}
430+
431+
// writing response
432+
// (jsonschema is in raw form (string) and non of the available format renders supports raw data output
433+
// with customizable content type setting in header -> custom handling)
434+
w.Header().Set(render.ContentType, render.ContentJSON+"; charset=UTF-8")
435+
w.Write([]byte(sb.String())) // will also call WriteHeader(http.StatusOK) automatically
436+
}
437+
}
438+
439+
// allImports retrieves all imports from given FileDescriptor including transitive imports (import
440+
// duplication can occur)
441+
func allImports(fileDesc protoreflect.FileDescriptor) []protoreflect.FileDescriptor {
442+
result := make([]protoreflect.FileDescriptor, 0)
443+
imports := fileDesc.Imports()
444+
for i := 0; i < imports.Len(); i++ {
445+
currentImport := imports.Get(i).FileDescriptor
446+
result = append(result, currentImport)
447+
result = append(result, allImports(currentImport)...)
448+
}
449+
return result
450+
}
451+
452+
// allFileDescriptorProtos retrieves all FileDescriptorProtos related to given models (including
453+
// all imported proto files)
454+
func allFileDescriptorProtos(knownModels []*client.ModelInfo) []*descriptorpb.FileDescriptorProto {
455+
// extract all FileDescriptors for given known models (including direct and transitive file imports)
456+
fileDescriptors := make(map[string]protoreflect.FileDescriptor) // using map for deduplication
457+
for _, knownModel := range knownModels {
458+
protoFile := knownModel.MessageDescriptor.ParentFile()
459+
fileDescriptors[protoFile.Path()] = protoFile
460+
for _, importProtoFile := range allImports(protoFile) {
461+
fileDescriptors[importProtoFile.Path()] = importProtoFile
462+
}
463+
}
464+
465+
// convert retrieved FileDescriptors to FileDescriptorProtos
466+
fileDescriptorProtos := make([]*descriptorpb.FileDescriptorProto, 0, len(knownModels))
467+
for _, fileDescriptor := range fileDescriptors {
468+
fileDescriptorProtos = append(fileDescriptorProtos, protodesc.ToFileDescriptorProto(fileDescriptor))
469+
}
470+
return fileDescriptorProtos
471+
}
472+
318473
// versionHandler returns version of Agent.
319474
func (p *Plugin) versionHandler(formatter *render.Render) http.HandlerFunc {
320475
return func(w http.ResponseWriter, req *http.Request) {

0 commit comments

Comments
 (0)