-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Labels
Description
Feature Description
The mock generator needs to handle protobuf map fields (map<K, V>
) which are currently not implemented.
Current State
Map fields are not explicitly handled in the generateMockFieldAssignments
switch statement, likely falling through to the default TODO case or being treated as regular message fields.
Background
In protobuf, maps are syntactic sugar for repeated message fields with special semantics:
map<string, string> metadata = 1;
// Is equivalent to:
message MetadataEntry {
string key = 1;
string value = 2;
}
repeated MetadataEntry metadata = 1;
Proposed Solution
1. Detect Map Fields
Add map field detection and generation:
case protoreflect.MessageKind:
if field.Desc.IsMap() {
g.generateMapField(gf, field, varName, fieldPath)
} else if field.Desc.IsList() {
// Existing repeated message logic
} else {
// Existing single message logic
}
2. Implement Map Generation
func (g *Generator) generateMapField(
gf *protogen.GeneratedFile,
field *protogen.Field,
varName string,
fieldPath string,
) {
fieldName := field.GoName
keyKind := field.Message.Fields[0].Desc.Kind()
valueKind := field.Message.Fields[1].Desc.Kind()
// Generate map initialization
gf.P(varName, ".", fieldName, " = map[",
g.getGoType(field.Message.Fields[0]), "][",
g.getGoType(field.Message.Fields[1]), "]{")
// Generate 2-3 entries by default
for i := 0; i < defaultMapSize; i++ {
key := g.generateMapKey(field.Message.Fields[0], i)
value := g.generateMapValue(field.Message.Fields[1], fieldPath, i)
gf.P(key, ": ", value, ",")
}
gf.P("}")
}
3. Key Generation
func (g *Generator) generateMapKey(keyField *protogen.Field, index int) string {
switch keyField.Desc.Kind() {
case protoreflect.StringKind:
return fmt.Sprintf(`"key_%d"`, index)
case protoreflect.Int32Kind, protoreflect.Int64Kind:
return fmt.Sprintf("%d", index)
case protoreflect.Uint32Kind, protoreflect.Uint64Kind:
return fmt.Sprintf("%d", index)
case protoreflect.BoolKind:
return fmt.Sprintf("%t", index%2 == 0)
default:
return `"key"`
}
}
4. Value Generation
func (g *Generator) generateMapValue(
valueField *protogen.Field,
fieldPath string,
index int,
) string {
valuePath := fmt.Sprintf("%s[%d]", fieldPath, index)
switch valueField.Desc.Kind() {
case protoreflect.StringKind:
return fmt.Sprintf(`selectStringExample("%s", generateString)`, valuePath)
case protoreflect.MessageKind:
// For message values, generate inline
return fmt.Sprintf("&%s{/* populated separately */}", valueField.Message.GoIdent)
case protoreflect.Int32Kind, protoreflect.Int64Kind:
return fmt.Sprintf("selectIntExample(\"%s\", %d)", valuePath, 42+index)
// ... other types
default:
return g.getDefaultValue(valueField)
}
}
5. Complex Map Values
For maps with message values, populate after initialization:
if valueKind == protoreflect.MessageKind {
// After map initialization, populate message values
for i := 0; i < defaultMapSize; i++ {
key := g.generateMapKey(field.Message.Fields[0], i)
valueVar := fmt.Sprintf("%s.%s[%s]", varName, fieldName, key)
g.generateMockFieldAssignments(gf, field.Message.Fields[1].Message, valueVar)
}
}
Common Map Patterns
Support common map patterns with smart defaults:
func (g *Generator) getMapSize(field *protogen.Field) int {
fieldName := strings.ToLower(string(field.Desc.Name()))
switch {
case strings.Contains(fieldName, "metadata"):
return 3 // More entries for metadata
case strings.Contains(fieldName, "headers"):
return 4 // HTTP headers typically have several
case strings.Contains(fieldName, "labels"):
return 2 // Labels/tags
default:
return defaultMapSize
}
}
Testing Requirements
Test Proto Files
message MapTestMessage {
// Basic maps
map<string, string> string_map = 1;
map<int32, string> int_string_map = 2;
map<string, int32> string_int_map = 3;
// Complex maps
map<string, NestedMessage> message_map = 4;
map<int64, TestEnum> enum_map = 5;
// Edge cases
map<bool, bool> bool_map = 6;
map<uint64, bytes> bytes_map = 7;
}
Test Cases
- Maps with all key types (string, int32, int64, uint32, uint64, bool)
- Maps with all value types (scalars, messages, enums)
- Empty maps (when configured)
- Large maps (10+ entries)
- Nested maps (message containing maps in map values)
Files Affected
internal/httpgen/mock_generator.go
- Add map detection and generationinternal/httpgen/testdata/proto/test_maps.proto
- Map test casesinternal/httpgen/testdata/golden/*_http_mock.pb.go
- Golden files
Success Criteria
- All valid map key types supported
- All value types supported (scalar, enum, message)
- Generated maps have appropriate number of entries
- Map keys are unique within each map
- Generated code compiles without errors
- Message values in maps are properly populated