Repository navigation
Generate JSON Schema deprecated from protobuf deprecated options - #165
Conversation
5ae97d8 to
658a0ef
Compare
JSON Schema draft 2020-12 defines a `deprecated` annotation keyword. Map the protobuf `deprecated` option onto it for messages and fields so consumers can surface deprecation in IDEs and docs.
658a0ef to
a9ea215
Compare
|
Bumping this, tagging a couple recent contributors to see if anyone has feedback. Didn't see any instructions for requesting reviewers in the readme. |
| ) error { | ||
| field := desc.Fields().Get(0) | ||
| p.setDescription(field, schema) | ||
| p.setDeprecated(field, schema) |
There was a problem hiding this comment.
This shouldn't be needed - the wrapper types underlying value won't be deprecated.
| switch options := desc.Options().(type) { | ||
| case *descriptorpb.MessageOptions: | ||
| deprecated = options.GetDeprecated() | ||
| case *descriptorpb.FieldOptions: |
There was a problem hiding this comment.
This also needs to also handle:
- enum / enum value deprecation.
- deprecation at the file level (this probably should be reflected in all messages/enum types in the file but not necessarily all fields of every message).
There was a problem hiding this comment.
deprecation at the file level (this probably should be reflected in all messages/enum types in the file but not necessarily all fields of every message).
What is your reasoning for this? When I have implemented deprecation in the past I have just marked anything defined in the file as deprecated. A field is deprecated if the file is deprecated imo.
There was a problem hiding this comment.
The thinking was only to propagate it down to where it is needed. Since JSON schema has no concept of file level deprecation, we'd have to copy it down to messages/enums but not any further. This also matches the behavior of code generators which just mark the types deprecated and not all of the fields.
There was a problem hiding this comment.
What would you expect for enum/enum value deprecation?
For enum deprecation, the enum is inlined into the field
"enumValue": {
"enum": [
"ENUM_VAL1",
"ENUM_VAL2",
"ENUM_VAL7"
],
"title": "Enum",
"type": "string"
},
So we could deprecate any field that referenced a deprecated enum
"enumValue": {
"enum": [
"ENUM_VAL1",
"ENUM_VAL2",
"ENUM_VAL7"
],
"title": "Enum",
"type": "string",
"deprecated": true
},
For enum values there is no way to directly deprecate them. I found some suggestions to use an anyOf
"anyOf": [
{ "enum": ["red", "amber"] },
{ "enum": ["green"], "deprecated": true }
]
Thoughts?
There was a problem hiding this comment.
This also matches the behavior of code generators which just mark the types deprecated and not all of the fields.
I checked the go code generator, and in this repo you can see the file deprecation deprecates fields
// Deprecated: The entire proto file buf/protoschema/test/v1/deprecated_file.proto is marked as deprecated.
func (x *DeprecatedFileTest) GetStringField() string {
if x != nil {
return x.StringField
}
return ""
}
mypy-protobuf (disclaimer, i wrote the deprecation generation code) deprecates fields when the file is deprecated.
There was a problem hiding this comment.
anyOf change pushed. Pushed file level deprecation for message enums, waiting on a decision on matching go/mypy-protobuf.
I had an LLM look at a few other languages for comparison
| Language | Marker | Message | Enum | Field | Enum value | Service / RPC |
|---|---|---|---|---|---|---|
| Go | // Deprecated: |
✅ | ✅ | ✅ | ✅ | ✅ |
| Python (mypy-protobuf) | @typing.deprecated(...) |
✅ | ✅ | ✅ | ✅ | — |
| TypeScript (protobuf-es) | @deprecated |
✅ | ✅ | ❌ | ❌ | ✅ / ❌ |
| Objective-C | GPB_DEPRECATED_MSG |
✅ | ✅ | ❌ | ❌ | — |
| C++ | [[deprecated]] |
❌ | ❌ | ❌ | ❌ | ❌ |
| Java | @java.lang.Deprecated |
❌ | ❌ | ❌ | ❌ | ❌ |
| C# | [ObsoleteAttribute] |
❌ | ❌ | ❌ | ❌ | ❌ |
| Kotlin | @kotlin.Deprecated |
❌ | ❌ | ❌ | ❌ | ❌ |
| PHP | @deprecated phpdoc |
❌ | ❌ | ❌ | ❌ | ❌ |
| Ruby | none in .rb |
❌ | ❌ | ❌ | ❌ | ❌ |
I clearly lean towards the go/mypy option. But can do whatever the maintainers want.
There was a problem hiding this comment.
Thanks for looking into that more. What is the behavior of JSON schema consumers (editors and other tools) when encountering a deprecated message if the field isn't marked deprecated as well? Do they warn based on field usage if the message is deprecated?
I'm fine to change the approach but it will make the generated schemas larger.
There was a problem hiding this comment.
Good thinking, I'll survey a couple of them.
There was a problem hiding this comment.
My personal use case is generating tooltip from json schema, in which case i would rather just check field deprecation before adding a strikethrough, instead of checking field and message deprecation
VSCode renders a tooltip on the whole message either way, no difference
Other tools either don't support deprecated, or pass it on.
I do think that for "correctness" a deprecated file means everything that can be deprecated inside it is deprecated. Otherwise you are always checking if a message is deprecated when checking if a field is.
There was a problem hiding this comment.
Just checking if you had made a decision, or had time to review again.
A file-level `option deprecated = true` deprecates everything declared in that file, so types declared there are marked even without their own option. Fields and enum values inherit through their parent type instead.
Mark a field whose enum type is deprecated, and split deprecated enum values into their own schemas: JSON Schema can only annotate a whole schema, not individual members of an enum array, so each deprecated value becomes its own `const` branch. Validation is unaffected -- every declared value is still accepted.
JSON Schema defines a
deprecatedannotation keyword. Map the protobufdeprecatedoption onto it for messages and fields.