Skip to content

Commit

Permalink
refactoring to make the public interface usable/maintainable
Browse files Browse the repository at this point in the history
  • Loading branch information
sudorandom committed Aug 17, 2024
1 parent bec64f8 commit 2abe8a5
Show file tree
Hide file tree
Showing 26 changed files with 397 additions and 331 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# FauxRPC
![](<assets/logo-wide.jpg>)

# FauxRPC
[![Go](https://github.com/sudorandom/fauxrpc/actions/workflows/go.yml/badge.svg)](https://github.com/sudorandom/fauxrpc/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/sudorandom/fauxrpc)](https://goreportcard.com/report/github.com/sudorandom/fauxrpc) [![Go Reference](https://pkg.go.dev/badge/github.com/sudorandom/fauxrpc.svg)](https://pkg.go.dev/github.com/sudorandom/fauxrpc)

FauxRPC is a powerful tool that empowers you to accelerate development and testing by effortlessly generating fake implementations of gRPC, gRPC-Web, Connect, and REST services. If you have a protobuf-based workflow, this tool could help.

## Why FauxRPC?
Expand Down
Binary file added assets/logo-square.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions cmd/fauxrpc/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"connectrpc.com/grpcreflect"
"connectrpc.com/vanguard"
"github.com/sudorandom/fauxrpc"
"github.com/sudorandom/fauxrpc/private/protobuf"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
Expand Down Expand Up @@ -37,13 +36,12 @@ func (c *RunCmd) Run(globals *Globals) error {
// TODO: Add --no-reflection option
// TODO: Load descriptors from stdin (assume protocol descriptors in binary format)
// TODO: way more options for data generator, including a stub service for registering stubs
generator := fauxrpc.NewDataGenerator()

serviceNames := []string{}
vgservices := []*vanguard.Service{}
registry.ForEachService(func(sd protoreflect.ServiceDescriptor) {
vgservice := vanguard.NewServiceWithSchema(
sd, protobuf.NewHandler(sd, generator),
sd, protobuf.NewHandler(sd),
vanguard.WithTargetProtocols(vanguard.ProtocolGRPC),
vanguard.WithTargetCodecs(vanguard.CodecProto))
vgservices = append(vgservices, vgservice)
Expand Down
24 changes: 0 additions & 24 deletions gen.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,7 @@
package fauxrpc

import (
"github.com/brianvoe/gofakeit/v7"
"google.golang.org/protobuf/types/dynamicpb"
)

const MaxNestedDepth = 20

type DataGenerator interface {
SetData(msg *dynamicpb.Message)
}

type dataGenerator struct {
faker *gofakeit.Faker
}

func NewDataGenerator() *dataGenerator {
return &dataGenerator{faker: gofakeit.New(0)}
}

func (g *dataGenerator) SetData(msg *dynamicpb.Message) {
// TODO: Lookup/resolve custom rules per field
// TODO: Lookup/resolve custom rules per type, starting with well-known
// TODO: Use known protovalidate rules as constraints
g.setDataOnMessage(msg, state{})
}

type state struct {
Depth int
}
Expand Down
44 changes: 27 additions & 17 deletions gen_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fauxrpc
import (
"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"github.com/brianvoe/gofakeit/v7"
"google.golang.org/protobuf/reflect/protoreflect"
)

type BytesHints struct {
Expand All @@ -15,32 +16,41 @@ type BytesHints struct {
Version bool
}

func GenerateBytes(faker *gofakeit.Faker, hints BytesHints) []byte {
if hints.Rules == nil {
return []byte(faker.HipsterSentence(3))
func generateBytesSimple() []byte {
return []byte(gofakeit.HipsterSentence(3))
}

func GenerateBytes(fd protoreflect.FieldDescriptor) []byte {
constraints := getResolver().ResolveFieldConstraints(fd)
if constraints == nil {
return generateBytesSimple()
}
rules := constraints.GetBytes()
if rules == nil {
return generateBytesSimple()
}

if hints.Rules.Const != nil {
return hints.Rules.Const
if rules.Const != nil {
return rules.Const
}
minLen, maxLen := uint64(0), uint64(20)
if hints.Rules.Len != nil {
minLen = *hints.Rules.Len
maxLen = *hints.Rules.Len
if rules.Len != nil {
minLen = *rules.Len
maxLen = *rules.Len
}
if hints.Rules.MinLen != nil {
minLen = *hints.Rules.MinLen
if rules.MinLen != nil {
minLen = *rules.MinLen
}
if hints.Rules.MaxLen != nil {
maxLen = *hints.Rules.MaxLen
if rules.MaxLen != nil {
maxLen = *rules.MaxLen
}
if hints.Rules.Pattern != nil {
return []byte(faker.Regex(*hints.Rules.Pattern))
if rules.Pattern != nil {
return []byte(gofakeit.Regex(*rules.Pattern))
}

if len(hints.Rules.In) > 0 {
return hints.Rules.In[faker.IntRange(0, len(hints.Rules.In)-1)]
if len(rules.In) > 0 {
return rules.In[gofakeit.IntRange(0, len(rules.In)-1)]
}

return []byte(faker.Sentence(int(maxLen / uint64(4)))[minLen:maxLen])
return []byte(gofakeit.Sentence(int(maxLen / uint64(4)))[minLen:maxLen])
}
90 changes: 27 additions & 63 deletions gen_fields.go
Original file line number Diff line number Diff line change
@@ -1,123 +1,87 @@
package fauxrpc

import (
"strings"

"github.com/bufbuild/protovalidate-go/resolver"
"github.com/brianvoe/gofakeit/v7"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"
)

func (g *dataGenerator) getFieldValue(field protoreflect.FieldDescriptor, st state) *protoreflect.Value {
r := resolver.DefaultResolver{}
constraints := r.ResolveFieldConstraints(field)

switch field.Kind() {
func getFieldValue(fd protoreflect.FieldDescriptor, st state) *protoreflect.Value {
switch fd.Kind() {
case protoreflect.MessageKind:
switch string(field.Message().FullName()) {
switch string(fd.Message().FullName()) {
case "google.protobuf.Duration":
// TODO: hints
return g.genGoogleDuration()
return genGoogleDuration()
case "google.protobuf.Timestamp":
// TODO: hints
return g.genGoogleTimestamp()
return genGoogleTimestamp()
case "google.protobuf.Any":
// TODO: hints
return nil
case "google.protobuf.Value":
// TODO: hints
return g.genGoogleValue()
return genGoogleValue()
default:
nested := dynamicpb.NewMessage(field.Message())
g.setDataOnMessage(nested, st.Inc())
nested := dynamicpb.NewMessage(fd.Message())
setDataOnMessage(nested, st.Inc())
v := protoreflect.ValueOf(nested)
return &v
}
case protoreflect.GroupKind:
nested := dynamicpb.NewMessage(field.Message())
g.setDataOnMessage(nested, st.Inc())
nested := dynamicpb.NewMessage(fd.Message())
setDataOnMessage(nested, st.Inc())
v := protoreflect.ValueOf(nested)
return &v
case protoreflect.BoolKind:
v := protoreflect.ValueOfBool(true)
return &v
case protoreflect.EnumKind:
values := field.Enum().Values()
idx := g.faker.IntRange(0, values.Len()-1)
values := fd.Enum().Values()
idx := gofakeit.IntRange(0, values.Len()-1)
v := protoreflect.ValueOfEnum(protoreflect.EnumNumber(idx))
return &v
case protoreflect.StringKind:
hints := StringHints{Rules: constraints.GetString_()}
lowerName := strings.ToLower(string(field.Name()))
switch {
case strings.Contains(lowerName, "firstname"):
hints.FirstName = true
case strings.Contains(lowerName, "lastname"):
hints.LastName = true
case strings.Contains(lowerName, "name"):
hints.Name = true
case strings.Contains(lowerName, "id"):
hints.UUID = true
case strings.Contains(lowerName, "token"):
hints.UUID = true
case strings.Contains(lowerName, "url"):
hints.URL = true
case strings.Contains(lowerName, "version"):
hints.Version = true
}
v := protoreflect.ValueOfString(GenerateString(g.faker, hints))
v := protoreflect.ValueOfString(GenerateString(fd))
return &v
case protoreflect.BytesKind:
hints := BytesHints{Rules: constraints.GetBytes()}
v := protoreflect.ValueOfBytes(GenerateBytes(g.faker, hints))
v := protoreflect.ValueOfBytes(GenerateBytes(fd))
return &v
case protoreflect.Int32Kind:
hints := Int32Hints{Rules: constraints.GetInt32()}
v := protoreflect.ValueOfInt32(GenerateInt32(g.faker, hints))
v := protoreflect.ValueOfInt32(GenerateInt32(fd))
return &v
case protoreflect.Sint32Kind:
hints := SInt32Hints{Rules: constraints.GetSint32()}
v := protoreflect.ValueOfInt32(GenerateSInt32(g.faker, hints))
v := protoreflect.ValueOfInt32(GenerateSInt32(fd))
return &v
case protoreflect.Sfixed32Kind:
hints := SFixedInt32Hints{Rules: constraints.GetSfixed32()}
v := protoreflect.ValueOfInt32(GenerateSFixedInt32(g.faker, hints))
v := protoreflect.ValueOfInt32(GenerateSFixedInt32(fd))
return &v
case protoreflect.Uint32Kind:
hints := UInt32Hints{Rules: constraints.GetUint32()}
v := protoreflect.ValueOfUint32(GenerateUInt32(g.faker, hints))
v := protoreflect.ValueOfUint32(GenerateUInt32(fd))
return &v
case protoreflect.Fixed32Kind:
hints := Fixed32Hints{Rules: constraints.GetFixed32()}
v := protoreflect.ValueOfUint32(GenerateFixed32(g.faker, hints))
v := protoreflect.ValueOfUint32(GenerateFixed32(fd))
return &v
case protoreflect.Int64Kind:
hints := Int64Hints{Rules: constraints.GetInt64()}
v := protoreflect.ValueOfInt64(GenerateInt64(g.faker, hints))
v := protoreflect.ValueOfInt64(GenerateInt64(fd))
return &v
case protoreflect.Sint64Kind:
hints := SInt64Hints{Rules: constraints.GetSint64()}
v := protoreflect.ValueOfInt64(GenerateSInt64(g.faker, hints))
v := protoreflect.ValueOfInt64(GenerateSInt64(fd))
return &v
case protoreflect.Sfixed64Kind:
hints := SFixed64Hints{Rules: constraints.GetSfixed64()}
v := protoreflect.ValueOfInt64(GenerateSFixed64(g.faker, hints))
v := protoreflect.ValueOfInt64(GenerateSFixed64(fd))
return &v
case protoreflect.Uint64Kind:
hints := UInt64Hints{Rules: constraints.GetUint64()}
v := protoreflect.ValueOfUint64(GenerateUInt64(g.faker, hints))
v := protoreflect.ValueOfUint64(GenerateUInt64(fd))
return &v
case protoreflect.Fixed64Kind:
hints := Fixed64Hints{Rules: constraints.GetFixed64()}
v := protoreflect.ValueOfUint64(GenerateFixed64(g.faker, hints))
v := protoreflect.ValueOfUint64(GenerateFixed64(fd))
return &v
case protoreflect.FloatKind:
hints := Float32Hints{Rules: constraints.GetFloat()}
v := protoreflect.ValueOfFloat32(GenerateFloat32(g.faker, hints))
v := protoreflect.ValueOfFloat32(GenerateFloat32(fd))
return &v
case protoreflect.DoubleKind:
hints := Float64Hints{Rules: constraints.GetDouble()}
v := protoreflect.ValueOfFloat64(GenerateFloat64(g.faker, hints))
v := protoreflect.ValueOfFloat64(GenerateFloat64(fd))
return &v
default:
return nil
Expand Down
30 changes: 18 additions & 12 deletions gen_fixed32.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,47 @@ import (

"buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
"github.com/brianvoe/gofakeit/v7"
"google.golang.org/protobuf/reflect/protoreflect"
)

type Fixed32Hints struct {
Rules *validate.Fixed32Rules
}

func GenerateFixed32(faker *gofakeit.Faker, hints Fixed32Hints) uint32 {
if hints.Rules == nil {
return faker.Uint32()
func GenerateFixed32(fd protoreflect.FieldDescriptor) uint32 {
constraints := getResolver().ResolveFieldConstraints(fd)
if constraints == nil {
return gofakeit.Uint32()
}
rules := constraints.GetFixed32()
if rules == nil {
return gofakeit.Uint32()
}

if hints.Rules.Const != nil {
return *hints.Rules.Const
if rules.Const != nil {
return *rules.Const
}
minVal, maxVal := uint32(0), uint32(math.MaxInt32)
if hints.Rules.GreaterThan != nil {
switch v := hints.Rules.GreaterThan.(type) {
if rules.GreaterThan != nil {
switch v := rules.GreaterThan.(type) {
case *validate.Fixed32Rules_Gt:
minVal = v.Gt + 1
case *validate.Fixed32Rules_Gte:
minVal = v.Gte
}
}
if hints.Rules.LessThan != nil {
switch v := hints.Rules.LessThan.(type) {
if rules.LessThan != nil {
switch v := rules.LessThan.(type) {
case *validate.Fixed32Rules_Lt:
maxVal = v.Lt + 1
case *validate.Fixed32Rules_Lte:
maxVal = v.Lte
}
}

if len(hints.Rules.In) > 0 {
return hints.Rules.In[faker.IntRange(0, len(hints.Rules.In)-1)]
if len(rules.In) > 0 {
return rules.In[gofakeit.IntRange(0, len(rules.In)-1)]
}

return uint32(faker.IntRange(int(minVal), int(maxVal)))
return uint32(gofakeit.IntRange(int(minVal), int(maxVal)))
}
Loading

0 comments on commit 2abe8a5

Please sign in to comment.