Protovalidate
FauxRPC uses protovalidate annotations to generate better fake data. The best way to understand is by showing.
Now let’s see what this looks like with protovalidate constraints:
syntax = "proto3";
import "buf/validate/validate.proto";
package greet.v1;
message GreetRequest {
string name = 1 [(buf.validate.field).string = {min_len: 3, max_len: 100}];
}
message GreetResponse {
string greeting = 1 [(buf.validate.field).string.pattern = "^Hello, [a-zA-Z]+$"];
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
With this new protobuf file, this is what FauxRPC might output now:
{
"greeting": "Hello, TWXxF"
}
This shows how protovalidate constraints enable FauxRPC to generate more realistic and contextually relevant fake data, aligning it closer to the expected behavior of your actual services.
Below is a buf.registry.owner.v1.User
message defined in protobuf. This example comes from the protobuf registry service for buf.build. Notice that the id field has protovalidate annotation saying that it’s actually expected to have a tuuid
format. TUUIDs are UUIDs but without the dashes. So a UUID of 7598a4f5-9a6a-4c68-9683-de54e21fb466
would look like 7598a4f59a6a4c689683de54e21fb466
in the TUUID format. Also, there’s a name field with a minimum/maximum length and a regex pattern.
// A name uniquely identifies a User, however name is mutable.
message User {
option (buf.registry.priv.extension.v1beta1.message).response_only = true;
// The id for the User.
string id = 1 [
(buf.validate.field).required = true,
(buf.validate.field).string.tuuid = true
];
// The time the User was created.
google.protobuf.Timestamp create_time = 2 [(buf.validate.field).required = true];
// The last time the User was updated.
google.protobuf.Timestamp update_time = 3 [(buf.validate.field).required = true];
// The name of the User.
//
// A name uniquely identifies a User, however name is mutable.
string name = 4 [
(buf.validate.field).required = true,
(buf.validate.field).string.min_len = 2,
(buf.validate.field).string.max_len = 32,
(buf.validate.field).string.pattern = "^[a-z][a-z0-9-]*[a-z0-9]$"
];
// The type of the User.
UserType type = 5 [
(buf.validate.field).required = true,
(buf.validate.field).enum.defined_only = true
];
// The state of the User.
UserState state = 6 [
(buf.validate.field).required = true,
(buf.validate.field).enum.defined_only = true
];
// The configurable description of the User.
string description = 7 [(buf.validate.field).string.max_len = 350];
// The configurable URL that represents the homepage for a User.
string url = 8 [
(buf.validate.field).string.uri = true,
(buf.validate.field).string.max_len = 255,
(buf.validate.field).ignore = IGNORE_IF_UNPOPULATED
];
// The verification status of the User.
UserVerificationStatus verification_status = 9 [
(buf.validate.field).required = true,
(buf.validate.field).enum.defined_only = true
];
}
Now let’s see the output that FauxRPC will give for this type:
$ buf build buf.build/bufbuild/registry -o bufbuild.registry.binpb
$ fauxrpc run --schema=./bufbuild.registry.binpb
$ buf curl --http2-prior-knowledge http://127.0.0.1:6660/buf.registry.owner.v1.UserService/ListUsers
{
"nextPageToken": "Food truck.",
"users": [
{
"id": "c4468393f926400d8880a264df9c284a",
"createTime": "2012-03-06T12:15:03.239463070Z",
"updateTime": "1990-10-29T13:12:31.224347086Z",
"name": "jexox",
"type": "USER_TYPE_STANDARD",
"description": "Tattooed taxidermy.",
"url": "http://www.productexploit.name/synergies/target"
},
{
"id": "0e4ca24f4ff54761b109daab0da1bea2",
"createTime": "1955-05-16T02:37:30.643378679Z",
"updateTime": "1923-08-28T04:28:43.330711919Z",
"name": "ya0",
"type": "USER_TYPE_STANDARD",
"state": "USER_STATE_INACTIVE",
"description": "Helvetica.",
"url": "https://www.centralengage.info/markets/scale/e-commerce/exploit",
"verificationStatus": "USER_VERIFICATION_STATUS_UNVERIFIED"
}
]
}
Hopefully, this gives you a good idea of what the output might look like. The better your validation rules, the better the FauxRPC data will be.