Skip to content

Commit

Permalink
enhancement: Migrate to new bufbuild/protovalidate (#2)
Browse files Browse the repository at this point in the history
Signed-off-by: Oğuzhan Durgun <[email protected]>
  • Loading branch information
oguzhand95 committed Sep 25, 2023
1 parent 82a199c commit 1600c74
Show file tree
Hide file tree
Showing 28 changed files with 328 additions and 151 deletions.
14 changes: 4 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ include tools/tools.mk
PROTOVALIDATE_MODULE := "buf.build/bufbuild/protovalidate"
PROTOS_DIR := "protos"
GENERATED_DIR := "gen"
TESTDATA_DIR = "internal/test/testdata"
TEST_DIR := "internal/test"

.PHONY: build
build: deps generate lint test compile install
Expand All @@ -21,7 +21,7 @@ generate: $(BUF) generate-buf generate-testdata

.PHONY: install
install:
@ go install cmd/protoc-gen-jsonschema
@ go install ./cmd/protoc-gen-jsonschema

.PHONY: lint
lint: $(BUF) $(GOLANGCI_LINT)
Expand All @@ -40,11 +40,5 @@ generate-buf: $(BUF)
@ rm -rf $(PROTOS_DIR)

.PHONY: generate-testdata
generate-testdata: $(BUF) export-test-deps
@ cd tools && $(BUF) generate --template=test.gen.yaml ../$(TESTDATA_DIR)
@ rm -rf $(TESTDATA_DIR)/buf

.PHONY: export-test-deps
export-test-deps: $(BUF)
@ rm -rf $(TESTDATA_DIR)/buf
@ $(BUF) export $(PROTOVALIDATE_MODULE) -o $(TESTDATA_DIR)
generate-testdata: $(BUF)
@ cd $(TEST_DIR) && $(BUF) generate .
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# protoc-gen-jsonschema

`protoc-gen-jsonschema` generates json schema from
[bufbuild/protovalidate](https://github.com/bufbuild/protovalidate) validation rules.
76 changes: 76 additions & 0 deletions cmd/protoc-gen-jsonschema-debug/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2021-2023 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"bytes"
"fmt"
"io"
"log"
"os"

pgs "github.com/lyft/protoc-gen-star/v2"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/pluginpb"

"github.com/cerbos/protoc-gen-jsonschema/internal/module"
)

const (
debugEnv = "PGC_DEBUG"
requestPath = "internal/test/code_generator_request.pb.bin"
)

func main() {
if _, exists := os.LookupEnv(debugEnv); exists {
if err := printRequest(requestPath); err != nil {
log.Fatalf("failed to print request: %s", err.Error())
}
}

reqFile, err := os.Open(requestPath)
if err != nil {
log.Fatalf("failed to open code generator request file: %s", err.Error())
}

resBytes := &bytes.Buffer{}
pgs.Init(
pgs.DebugEnv(debugEnv),
pgs.ProtocInput(reqFile),
pgs.ProtocOutput(resBytes),
).RegisterModule(module.New()).Render()

res := &pluginpb.CodeGeneratorResponse{}
if err := proto.Unmarshal(resBytes.Bytes(), res); err != nil {
log.Fatalf("failed to unmarshal code generator response: %s", err.Error())
}

for _, f := range res.File {
log.Printf("%s\n", *f.Name)
log.Printf("\n%s", *f.Content)
log.Printf("--------------------------------------\n")
}
}

func printRequest(path string) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open code generator request file: %w", err)
}

reqBytes, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("failed to read code generator request bytes: %w", err)
}

req := &pluginpb.CodeGeneratorRequest{}
if err := proto.Unmarshal(reqBytes, req); err != nil {
return fmt.Errorf("failed to unmarshal code generator response: %w", err)
}

log.Printf("%s", protojson.Format(req))
log.Printf("--------------------------------------\n")
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
package main

import (
"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/module"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/internal/module"
)

func main() {
pgs.Init(pgs.DebugEnv("DEBUG")).RegisterModule(module.New()).Render()
pgs.Init(pgs.DebugEnv("PGC_DEBUG")).RegisterModule(module.New()).Render()
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package jsonschema

//nolint:govet
type GenericSchema struct {
ID string `json:"$id,omitempty"`
Version string `json:"$schema,omitempty"`
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package jsonschema

//nolint:govet
type ObjectSchema struct {
GenericSchema
MaxProperties *uint64 `json:"maxProperties,omitempty"`
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
StringFormatURIReference StringFormat = "uri-reference"
)

//nolint:govet
type StringSchema struct {
GenericSchema
Const *string `json:"const,omitempty"`
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
package module

import (
"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
"github.com/envoyproxy/protoc-gen-validate/validate"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/gen/pb/buf/validate"
"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

func (m *Module) schemaForMap(value pgs.FieldTypeElem, rules *validate.MapRules) (jsonschema.Schema, bool) {
required := false
func (m *Module) schemaForMap(value pgs.FieldTypeElem, rules *validate.MapRules) jsonschema.Schema {
schema := jsonschema.NewObjectSchema()
schema.AdditionalProperties, _ = m.schemaForElement(value, rules.GetValues())

if rules != nil {
if rules.GetKeys().GetString_() != nil {
schema.PropertyNames, _ = m.schemaForString(rules.GetKeys().GetString_())
schema.PropertyNames, _ = m.schemaForString(rules.GetKeys().GetString_(), false) // TODO
}

if rules.MaxPairs != nil {
Expand All @@ -25,15 +25,13 @@ func (m *Module) schemaForMap(value pgs.FieldTypeElem, rules *validate.MapRules)

if rules.MinPairs != nil {
schema.MinProperties = jsonschema.Size(rules.GetMinPairs())
required = !rules.GetIgnoreEmpty()
}
}

return schema, required
return schema
}

func (m *Module) schemaForRepeated(item pgs.FieldTypeElem, rules *validate.RepeatedRules) (jsonschema.Schema, bool) {
required := false
func (m *Module) schemaForRepeated(item pgs.FieldTypeElem, rules *validate.RepeatedRules) jsonschema.Schema {
schema := jsonschema.NewArraySchema()
schema.Items, _ = m.schemaForElement(item, rules.GetItems())

Expand All @@ -44,25 +42,24 @@ func (m *Module) schemaForRepeated(item pgs.FieldTypeElem, rules *validate.Repea

if rules.MinItems != nil {
schema.MinItems = jsonschema.Size(rules.GetMinItems())
required = !rules.GetIgnoreEmpty()
}

if rules.Unique != nil {
schema.UniqueItems = rules.GetUnique()
}
}

return schema, required
return schema
}

func (m *Module) schemaForElement(element pgs.FieldTypeElem, rules *validate.FieldRules) (jsonschema.Schema, bool) {
func (m *Module) schemaForElement(element pgs.FieldTypeElem, constraints *validate.FieldConstraints) (jsonschema.Schema, bool) {
if element.IsEmbed() {
return m.schemaForEmbed(element.Embed(), rules)
return m.schemaForEmbed(element.Embed(), constraints)
}

if element.IsEnum() {
return m.schemaForEnum(element.Enum(), rules.GetEnum())
return m.schemaForEnum(element.Enum(), constraints.GetEnum())
}

return m.schemaForScalar(element.ProtoType(), rules)
return m.schemaForScalar(element.ProtoType(), constraints)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
package module

import (
"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
"github.com/envoyproxy/protoc-gen-validate/validate"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/gen/pb/buf/validate"
"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

func (m *Module) defineEnum(enum pgs.Enum) *jsonschema.StringSchema {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ package module
import (
"fmt"

"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
"github.com/envoyproxy/protoc-gen-validate/validate"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/gen/pb/buf/validate"
"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

func (m *Module) defineMessage(message pgs.Message) jsonschema.NonTrivialSchema {
Expand Down Expand Up @@ -47,51 +48,47 @@ func (m *Module) schemaForField(field pgs.Field) (jsonschema.Schema, bool) {
m.Push(fmt.Sprintf("field:%s", field.Name()))
defer m.Pop()

rules := &validate.FieldRules{}
_, err := field.Extension(validate.E_Rules, rules)
m.CheckErr(err, "unable to read validation rules from field")
constraints := &validate.FieldConstraints{}
_, err := field.Extension(validate.E_Field, constraints)
m.CheckErr(err, "unable to read validation constraints from field")

var schema jsonschema.Schema
var required bool

switch {
case field.Type().IsEmbed():
schema, required = m.schemaForEmbed(field.Type().Embed(), rules)

schema, required = m.schemaForEmbed(field.Type().Embed(), constraints)
case field.Type().IsEnum():
schema, required = m.schemaForEnum(field.Type().Enum(), rules.GetEnum())

schema, required = m.schemaForEnum(field.Type().Enum(), constraints.GetEnum())
case field.Type().IsMap():
schema, required = m.schemaForMap(field.Type().Element(), rules.GetMap())

schema = m.schemaForMap(field.Type().Element(), constraints.GetMap())
case field.Type().IsRepeated():
schema, required = m.schemaForRepeated(field.Type().Element(), rules.GetRepeated())

schema = m.schemaForRepeated(field.Type().Element(), constraints.GetRepeated())
required = constraints.Required
default:
schema, required = m.schemaForScalar(field.Type().ProtoType(), rules)
schema, required = m.schemaForScalar(field.Type().ProtoType(), constraints)
}

return schema, required && !field.InOneOf()
}

func (m *Module) schemaForEmbed(embed pgs.Message, rules *validate.FieldRules) (jsonschema.Schema, bool) {
func (m *Module) schemaForEmbed(embed pgs.Message, constraints *validate.FieldConstraints) (jsonschema.Schema, bool) {
if embed.IsWellKnown() {
return m.schemaForWellKnownType(embed.WellKnownType(), rules)
return m.schemaForWellKnownType(embed.WellKnownType(), constraints)
}

return m.schemaForMessage(embed, rules.GetMessage())
return m.schemaForMessage(embed), constraints.Required
}

func (m *Module) schemaForMessage(message pgs.Message, rules *validate.MessageRules) (jsonschema.Schema, bool) {
return m.messageRef(message), rules.GetRequired()
func (m *Module) schemaForMessage(message pgs.Message) jsonschema.Schema {
return m.messageRef(message)
}

func (m *Module) schemaForOneOf(oneOf pgs.OneOf) jsonschema.NonTrivialSchema {
required := false
_, err := oneOf.Extension(validate.E_Required, &required)
constraint := validate.OneofConstraints{}
_, err := oneOf.Extension(validate.E_Oneof, &constraint)
m.CheckErr(err, "unable to read required option from oneof")

if !required {
if !*constraint.Required {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
"fmt"
"strings"

"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

type Module struct {
Expand All @@ -26,7 +27,7 @@ func (*Module) Name() string {
return "jsonschema"
}

func (m *Module) Execute(targets map[string]pgs.File, pkgs map[string]pgs.Package) []pgs.Artifact {
func (m *Module) Execute(targets map[string]pgs.File, _ map[string]pgs.Package) []pgs.Artifact {
baseURL := m.Parameters().StrDefault("baseurl", "https://protoc-gen-jsonschema.cerbos.dev/")
if !strings.HasSuffix(baseURL, "/") {
baseURL += "/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"fmt"
"strings"

"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
pgs "github.com/lyft/protoc-gen-star/v2"

"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

type namedEntity interface {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ package module
import (
"encoding/json"

"github.com/cerbos/cerbos/hack/tools/protoc-gen-jsonschema/jsonschema"
"github.com/envoyproxy/protoc-gen-validate/validate"
pgs "github.com/lyft/protoc-gen-star/v2"
"google.golang.org/protobuf/proto"

"github.com/cerbos/protoc-gen-jsonschema/gen/pb/buf/validate"
"github.com/cerbos/protoc-gen-jsonschema/internal/jsonschema"
)

const (
signedDecimalString = `^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`
unsignedDecimalString = `^(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`
)

//nolint:tagliatelle
type numericRules struct {
Const jsonschema.Number `json:"const,omitempty"`
Lt jsonschema.Number `json:"lt,omitempty"`
Expand All @@ -28,12 +30,13 @@ type numericRules struct {
IgnoreEmpty bool `json:"ignore_empty,omitempty"`
}

func (m *Module) schemaForNumericScalar(numeric pgs.ProtoType, fieldRules *validate.FieldRules) (jsonschema.Schema, bool) {
func (m *Module) schemaForNumericScalar(numeric pgs.ProtoType, constraints *validate.FieldConstraints) (jsonschema.Schema, bool) {
required := false
value := m.valueSchemaForNumericScalar(numeric)
schemas := []jsonschema.NonTrivialSchema{m.stringValueSchemaForNumericScalar(numeric, value)}
rules := m.numericRules(numeric, fieldRules)
rules := m.numericRules(numeric, constraints)

//nolint:nestif
if rules != nil {
if rules.Const != nil {
value.Const = rules.Const
Expand Down Expand Up @@ -123,7 +126,7 @@ func (m *Module) stringValueSchemaForNumericScalar(numeric pgs.ProtoType, value
return jsonschema.OneOf(value, stringValue)
}

func (m *Module) numericRules(numeric pgs.ProtoType, fieldRules *validate.FieldRules) *numericRules {
func (m *Module) numericRules(numeric pgs.ProtoType, fieldRules *validate.FieldConstraints) *numericRules {
var source proto.Message

switch numeric {
Expand Down
Loading

0 comments on commit 1600c74

Please sign in to comment.