Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 73 additions & 35 deletions cmd/mdatagen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,50 +81,90 @@ config:
description: The endpoint to listen on
default: "localhost:4317"
timeout:
type: string
format: duration
type: duration
description: Request timeout duration
default: "30s"
tls:
$ref: go.opentelemetry.io/collector/config/configtls.server_config
required: [endpoint]
```

The `config` section is based on [JSON Schema standard](https://json-schema.org/) (draft 2020-12) and supports:
The `config` section uses Go-centric types directly in the `type` field, and also supports:

- **Standard JSON Schema types**: string, number, integer, boolean, object, array, null
- **Validation constraints**: minLength, maxLength, pattern, minimum, maximum, enum, etc.
- **References**: Internal (`$ref: definition_name`), external (`$ref: package.path.type`), or relative (`$ref: ./internal/config.type`)
- **Reusable definitions**: Define common schemas in `$defs` and reference them with `$ref`
- **Schema composition**: Use `allOf` for complex configurations
- **Extended type aliases**: first-class aliases that expand to the correct JSON Schema shape and Go type automatically (see table below)

#### Extended type aliases

Instead of combining `type`+`format` or `type`+`x-customType` by hand, you can use an alias directly as the `type` value:

| Alias | Go type | JSON Schema representation |
|---|---|-----------------------------------|
| `rune` | `rune` | `integer` |
| `byte` | `byte` | `integer` |
| `uint` | `uint` | `integer` |
| `int8` | `int8` | `integer` |
| `uint8` | `uint8` | `integer` |
| `int16` | `int16` | `integer` |
| `uint16` | `uint16` | `integer` |
| `int32` | `int32` | `integer` |
| `uint32` | `uint32` | `integer` |
| `int64` | `int64` | `integer` |
| `uint64` | `uint64` | `integer` |
| `float32` | `float32` | `number` |
| `float64` | `float64` | `number` |
| `duration` | `time.Duration` | `string` with Go duration pattern |
| `time` | `time.Time` | `string` with `format: date-time` |
| `opaque_string` | `configopaque.String` | `string` |
| `opaque_map` | `configopaque.MapList` | `object` of name/value pairs |
| `id` | `component.ID` | `string` |

Example:

#### Supported types

##### Primitive types

Use Go type names directly in the `type` field:

| Type | Go type | JSON Schema type |
|------|---------|-----------------|
| `string` | `string` | `string` |
| `bool` | `bool` | `boolean` |
| `int` | `int` | `integer` |
| `int8` | `int8` | `integer` |
| `int16` | `int16` | `integer` |
| `int32` | `int32` | `integer` |
| `int64` | `int64` | `integer` |
| `uint` | `uint` | `integer` |
| `uint8` | `uint8` | `integer` |
| `uint16` | `uint16` | `integer` |
| `uint32` | `uint32` | `integer` |
| `uint64` | `uint64` | `integer` |
| `byte` | `byte` | `integer` |
| `rune` | `rune` | `integer` |
| `float32` | `float32` | `number` |
| `float64` | `float64` | `number` |
| `any` | `any` | *(no type constraint)* |

##### Container types

| Type | Go type | JSON Schema type | Notes |
|------|---------|-----------------|-------|
| `object` | struct | `object` | Requires `properties:` |
| `slice` | `[]T` | `array` | Requires `values:` for the element type |
| `map` | `map[string]T` | `object` | Requires `values:` for the value type |

Example using container types:

```yaml
config:
type: object
properties:
endpoints:
type: slice
values:
type: string
description: List of endpoints to connect to.
headers:
type: map
values:
type: string
description: Extra HTTP headers to attach to each request.
tls:
$ref: go.opentelemetry.io/collector/config/configtls.server_config
```

##### Alias types

Shorthand types that expand to a primitive or container type with additional annotations:

| Alias | Go type | JSON Schema representation | Notes |
|-------|---------|---------------------------|-------|
| `float` | `float32` | `number` | Shorthand for `float32` |
| `double` | `float64` | `number` | Shorthand for `float64` |
| `duration` | `time.Duration` | `string` with Go duration pattern | e.g. `"30s"`, `"1h30m"` |
| `time` | `time.Time` | `string` with `format: date-time` | RFC 3339 format |
| `opaque_string` | `configopaque.String` | `string` | Masked in logs |
| `component_id` | `component.ID` | `string` | Collector component ID |
| `opaque_map` | `configopaque.MapList` | `object` of name/value pairs | Values masked in logs |

Example using alias types:

```yaml
config:
Expand All @@ -140,8 +180,6 @@ config:
type: opaque_string
```

Aliases are additive: existing uses of standard JSON Schema types, `format`, and `x-customType` remain supported without migration.

### Metrics Builder Configuration

For receivers, scrapers, and other components that emit metrics, `mdatagen` can generate metrics builder
Expand Down Expand Up @@ -172,7 +210,7 @@ metrics:
description: Number of received requests.
unit: "{request}"
sum:
value_type: int
values: int
monotonic: true
aggregation_temporality: cumulative
attributes: [status_code]
Expand Down
22 changes: 8 additions & 14 deletions cmd/mdatagen/internal/cfggen/docgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,18 @@ func CfgDocType(cfg *ConfigMetadata) string {
return "string (one of: " + strings.Join(vals, ", ") + ")"
}
return "string"
case "integer":
return "int"
case "number":
return "float"
case "boolean":
return "bool"
case "array":
if cfg.Items != nil {
return "[]" + CfgDocType(cfg.Items)
case "slice":
if cfg.Values != nil {
return "[]" + CfgDocType(cfg.Values)
}
return "[]any"
case "object":
if cfg.AdditionalProperties != nil {
return "map[string]" + CfgDocType(cfg.AdditionalProperties)
case "map":
if cfg.Values != nil {
return "map[string]" + CfgDocType(cfg.Values)
}
return "object"
return "map[string]any"
default:
return "any"
return cfg.Type
}
}

Expand Down
18 changes: 9 additions & 9 deletions cmd/mdatagen/internal/cfggen/docgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ func TestCfgIsObject(t *testing.T) {
}{
{"nil", nil, false},
{"primitive string", &ConfigMetadata{Type: "string"}, false},
{"array of strings", &ConfigMetadata{Type: "array", Items: &ConfigMetadata{Type: "string"}}, false},
{"slice of strings", &ConfigMetadata{Type: "slice", Values: &ConfigMetadata{Type: "string"}}, false},
{"inline object", &ConfigMetadata{Type: "object", Properties: map[string]*ConfigMetadata{"x": {Type: "string"}}}, true},
{"ref-resolved object", &ConfigMetadata{Type: "object", Ref: "confighttp.ServerConfig", Properties: map[string]*ConfigMetadata{"port": {Type: "integer"}}}, true},
{"map without properties", &ConfigMetadata{Type: "object", AdditionalProperties: &ConfigMetadata{Type: "string"}}, false},
{"map without properties", &ConfigMetadata{Type: "map", Values: &ConfigMetadata{Type: "string"}}, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -140,15 +140,15 @@ func TestCfgDocType(t *testing.T) {
{"datetime via GoType", &ConfigMetadata{Type: "string", GoType: "time.Time"}, "datetime"},
{"datetime via Format", &ConfigMetadata{Type: "string", Format: "date-time"}, "datetime"},
{"enum string", &ConfigMetadata{Type: "string", Enum: []any{"a", "b"}}, "string (one of: a, b)"},
{"integer", &ConfigMetadata{Type: "integer"}, "int"},
{"number", &ConfigMetadata{Type: "number"}, "float"},
{"boolean", &ConfigMetadata{Type: "boolean"}, "bool"},
{"array of string", &ConfigMetadata{Type: "array", Items: &ConfigMetadata{Type: "string"}}, "[]string"},
{"array of any", &ConfigMetadata{Type: "array"}, "[]any"},
{"map of string", &ConfigMetadata{Type: "object", AdditionalProperties: &ConfigMetadata{Type: "string"}}, "map[string]string"},
{"int", &ConfigMetadata{Type: "int"}, "int"},
{"float64", &ConfigMetadata{Type: "float64"}, "float64"},
{"bool", &ConfigMetadata{Type: "bool"}, "bool"},
{"array of string", &ConfigMetadata{Type: "slice", Values: &ConfigMetadata{Type: "string"}}, "[]string"},
{"array of any", &ConfigMetadata{Type: "slice"}, "[]any"},
{"map of string", &ConfigMetadata{Type: "map", Values: &ConfigMetadata{Type: "string"}}, "map[string]string"},
{"inline object", &ConfigMetadata{Type: "object", Properties: map[string]*ConfigMetadata{"x": {Type: "string"}}}, "object"},
{"plain object no props", &ConfigMetadata{Type: "object"}, "object"},
{"unknown type", &ConfigMetadata{Type: "unknown"}, "any"},
{"unknown type", &ConfigMetadata{Type: "unknown"}, "unknown"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
Loading
Loading