Skip to content

Commit

Permalink
feat: Add k8s well-known messages
Browse files Browse the repository at this point in the history
  • Loading branch information
uanid committed Sep 18, 2023
1 parent 6b058ee commit 1cf500e
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 25 deletions.
138 changes: 124 additions & 14 deletions pkg/modules/1_middleend_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package modules

import (
"encoding/json"
"slices"

pgs "github.com/lyft/protoc-gen-star/v2"
"github.com/pubg/protoc-gen-jsonschema/pkg/jsonschema"
Expand Down Expand Up @@ -60,6 +61,117 @@ func buildFromMessage(pluginOptions *proto.PluginOptions, message pgs.Message, m
return schema
}

func buildFromWellKnownMessage(pluginOptions *proto.PluginOptions, message pgs.Message, mo *proto.MessageOptions) *jsonschema.Schema {
baseSchema := buildFromMessage(pluginOptions, message, mo)

wellKnownType := getWellKnownMessageType(message)
if wellKnownType == WellKnownMessageTypeNone {
panic("not well known type")
}
switch wellKnownType {
case WellKnownMessageTypeK8sIntOrString:
schema := &jsonschema.Schema{}
schema.Title = baseSchema.Title
schema.Description = baseSchema.Description
schema.OneOf = []*jsonschema.Schema{
{Type: "string"},
{Type: "integer"},
}
return schema
case WellKnownMessageTypeK8sVolume:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.VolumeSource"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "volumeSource")
baseSchema.Properties.Delete("volumeSource")
case WellKnownMessageTypeK8sSecretProjection:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sConfigMapVolumeSource:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sConfigMapProjection:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sConfigMapKeySelector:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sSecretKeySelector:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sConfigMapEnvSource:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sSecretEnvSource:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.LocalObjectReference"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "localObjectReference")
baseSchema.Properties.Delete("localObjectReference")
case WellKnownMessageTypeK8sProbe:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.ProbeHandler"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "handler")
baseSchema.Properties.Delete("handler")
case WellKnownMessageTypeK8sEphemeralContainer:
baseSchema.OneOf = []*jsonschema.Schema{{Ref: ".k8s.io.api.core.v1.EphemeralContainerCommon"}}
baseSchema.Required = deletePropertyInRequired(baseSchema.Required, "ephemeralContainerCommon")
baseSchema.Properties.Delete("ephemeralContainerCommon")
}
return baseSchema
}

type WellKnownMessageType int

const (
WellKnownMessageTypeNone WellKnownMessageType = iota
WellKnownMessageTypeK8sIntOrString
WellKnownMessageTypeK8sVolume
WellKnownMessageTypeK8sSecretProjection
WellKnownMessageTypeK8sConfigMapVolumeSource
WellKnownMessageTypeK8sConfigMapProjection
WellKnownMessageTypeK8sConfigMapKeySelector
WellKnownMessageTypeK8sSecretKeySelector
WellKnownMessageTypeK8sConfigMapEnvSource
WellKnownMessageTypeK8sSecretEnvSource
WellKnownMessageTypeK8sProbe
WellKnownMessageTypeK8sEphemeralContainer
)

func isWellKnownMessage(message pgs.Message) bool {
return getWellKnownMessageType(message) != WellKnownMessageTypeNone
}

func getWellKnownMessageType(message pgs.Message) WellKnownMessageType {
switch message.FullyQualifiedName() {
case ".k8s.io.apimachinery.pkg.util.intstr.IntOrString":
return WellKnownMessageTypeK8sIntOrString
case ".k8s.io.api.core.v1.Volume":
return WellKnownMessageTypeK8sVolume
case ".k8s.io.api.core.v1.SecretProjection":
return WellKnownMessageTypeK8sSecretProjection
case ".k8s.io.api.core.v1.ConfigMapVolumeSource":
return WellKnownMessageTypeK8sConfigMapVolumeSource
case ".k8s.io.api.core.v1.ConfigMapProjection":
return WellKnownMessageTypeK8sConfigMapProjection
case ".k8s.io.api.core.v1.ConfigMapKeySelector":
return WellKnownMessageTypeK8sConfigMapKeySelector
case ".k8s.io.api.core.v1.SecretKeySelector":
return WellKnownMessageTypeK8sSecretKeySelector
case ".k8s.io.api.core.v1.ConfigMapEnvSource":
return WellKnownMessageTypeK8sConfigMapEnvSource
case ".k8s.io.api.core.v1.SecretEnvSource":
return WellKnownMessageTypeK8sSecretEnvSource
case ".k8s.io.api.core.v1.Probe":
return WellKnownMessageTypeK8sProbe
case ".k8s.io.api.core.v1.EphemeralContainer":
return WellKnownMessageTypeK8sEphemeralContainer
}

return WellKnownMessageTypeNone
}

func buildFromMessageField(field pgs.Field, fo *proto.FieldOptions) *jsonschema.Schema {
schema := &jsonschema.Schema{}
schema.Title = proto.GetTitleOrEmpty(fo)
Expand Down Expand Up @@ -211,7 +323,7 @@ func buildFromWellKnownField(field pgs.Field, fo *proto.FieldOptions) *jsonschem
schema.Title = proto.GetTitleOrEmpty(fo)
schema.Description = proto.GetDescriptionOrComment(field, fo)

wellKnownType := getWellKnownType(field)
wellKnownType := getWellKnownFieldType(field)
if wellKnownType == WellKnownTypeNone {
panic("not well known type")
}
Expand All @@ -229,11 +341,6 @@ func buildFromWellKnownField(field pgs.Field, fo *proto.FieldOptions) *jsonschem
schema.Type = "object"
case WellKnownTypeNullValue:
schema.Type = "null"
case WellKnownTypeK8sIntOrString:
schema.OneOf = []*jsonschema.Schema{
{Type: "string"},
{Type: "integer"},
}
}

if field.Type().IsRepeated() {
Expand All @@ -244,22 +351,21 @@ func buildFromWellKnownField(field pgs.Field, fo *proto.FieldOptions) *jsonschem
return schema
}

func isWellKnownType(field pgs.Field) bool {
return getWellKnownType(field) != WellKnownTypeNone
func isWellKnownField(field pgs.Field) bool {
return getWellKnownFieldType(field) != WellKnownTypeNone
}

type WellKnownType int
type WellKnownFieldType int

const (
WellKnownTypeNone WellKnownType = iota
WellKnownTypeNone WellKnownFieldType = iota
WellKnownTypeTimestamp
WellKnownTypeDuration
WellKnownTypeAny
WellKnownTypeNullValue
WellKnownTypeK8sIntOrString
)

func getWellKnownType(field pgs.Field) WellKnownType {
func getWellKnownFieldType(field pgs.Field) WellKnownFieldType {
if field.Type().IsMap() || field.Type().ProtoType() != pgs.MessageT {
return WellKnownTypeNone
}
Expand All @@ -279,8 +385,6 @@ func getWellKnownType(field pgs.Field) WellKnownType {
return WellKnownTypeAny
case ".google.protobuf.NullValue":
return WellKnownTypeNullValue
case ".k8s.io.apimachinery.pkg.util.intstr.IntOrString":
return WellKnownTypeK8sIntOrString
default:
return WellKnownTypeNone
}
Expand Down Expand Up @@ -334,3 +438,9 @@ func isInt64(protoType pgs.ProtoType) bool {
}
return false
}

func deletePropertyInRequired(required []string, item string) []string {
return slices.DeleteFunc(required, func(val string) bool {
return val == item
})
}
12 changes: 9 additions & 3 deletions pkg/modules/1_middleend_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ func (v *MiddleendVisitor) VisitMessage(message pgs.Message) (pgs.Visitor, error
return nil, nil
}

schema := buildFromMessage(v.pluginOptions, message, mo)
var schema *jsonschema.Schema
// if message is well-known type
if isWellKnownMessage(message) {
schema = buildFromWellKnownMessage(v.pluginOptions, message, mo)
} else {
schema = buildFromMessage(v.pluginOptions, message, mo)
}
v.registry.AddSchema(message.FullyQualifiedName(), schema)
return v, nil
}
Expand All @@ -44,8 +50,8 @@ func (v *MiddleendVisitor) VisitField(field pgs.Field) (pgs.Visitor, error) {
return nil, nil
}

// TODO: if field is well-known type
if isWellKnownType(field) {
// if field is well-known type
if isWellKnownField(field) {
schema := buildFromWellKnownField(field, fo)
v.registry.AddSchema(field.FullyQualifiedName(), schema)
return v, nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/modules/2_backend_optimizer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package modules

import (
"fmt"

pgs "github.com/lyft/protoc-gen-star/v2"
"github.com/pubg/protoc-gen-jsonschema/pkg/jsonschema"
"github.com/pubg/protoc-gen-jsonschema/pkg/proto"
Expand Down Expand Up @@ -66,6 +68,9 @@ func (o *OptimizerImpl) getEntrypointMessage(messages []pgs.Message, fileOptions

func (o *OptimizerImpl) increaseSchemaRefCount(registry *jsonschema.Registry, ref string) {
schema := registry.GetSchema(ref)
if schema == nil {
panic(fmt.Sprintf("schema not found: %s", ref))
}

rawValue := schema.GetExtrasItem(refCountKey)
if rawValue == nil {
Expand Down
20 changes: 12 additions & 8 deletions testdata/cases/wellknown-k8s/test.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/.tests.Values",
"$defs": {
".k8s.io.apimachinery.pkg.util.intstr.IntOrString": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "IntOrString is a type that can hold an int32 or a string. When used in\n JSON or YAML marshalling and unmarshalling, it produces or consumes the\n inner type. This allows you to have, for example, a JSON field that can\n accept a name or number.\n TODO: Rename to Int32OrString\n\n +protobuf=true\n +protobuf.options.(gogoproto.goproto_stringer)=false\n +k8s:openapi-gen=true"
},
".tests.Values": {
"properties": {
"int_or_string": {
Expand All @@ -15,14 +26,7 @@
"description": "TODO: import k8s proto files"
},
".tests.Values.int_or_string": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
"$ref": "#/$defs/.k8s.io.apimachinery.pkg.util.intstr.IntOrString"
}
},
"type": "object"
Expand Down

0 comments on commit 1cf500e

Please sign in to comment.