-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom annotations for bundle-specific JSON schema fields #1957
Changes from 11 commits
9b588c0
12c4373
049e11b
8016d41
0221c28
a3fdc8d
79a88e9
c3d049f
b076813
073aeca
a6c45d5
d164968
00164a8
a579b1c
e15107f
9a55037
16042b5
0171513
d2bfb67
8f59695
40505b8
d3b30f7
43c4b58
aed0e0a
5194889
8a33fb3
6bc9664
1b31c09
7fb1ed8
9f3ec3d
3231cff
c619a73
3049184
0037671
7d8ae48
34d7484
386c7d3
835fb05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,169 @@ | ||||||
package main | ||||||
|
||||||
import ( | ||||||
"bytes" | ||||||
"os" | ||||||
"reflect" | ||||||
"strings" | ||||||
|
||||||
"github.com/ghodss/yaml" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use This gives you some control over the node style if you need it (e.g. use blocks for all descriptions). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated to |
||||||
yaml3 "gopkg.in/yaml.v3" | ||||||
|
||||||
"github.com/databricks/cli/libs/dyn" | ||||||
"github.com/databricks/cli/libs/dyn/convert" | ||||||
"github.com/databricks/cli/libs/dyn/merge" | ||||||
"github.com/databricks/cli/libs/dyn/yamlloader" | ||||||
"github.com/databricks/cli/libs/dyn/yamlsaver" | ||||||
"github.com/databricks/cli/libs/jsonschema" | ||||||
) | ||||||
|
||||||
type annotation struct { | ||||||
Description string `json:"description,omitempty"` | ||||||
MarkdownDescription string `json:"markdown_description,omitempty"` | ||||||
Title string `json:"title,omitempty"` | ||||||
Default any `json:"default,omitempty"` | ||||||
Enum []any `json:"enum,omitempty"` | ||||||
} | ||||||
|
||||||
type annotationHandler struct { | ||||||
pietern marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
ref annotationFile | ||||||
empty annotationFile | ||||||
} | ||||||
|
||||||
type annotationFile map[string]map[string]annotation | ||||||
pietern marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
const Placeholder = "PLACEHOLDER" | ||||||
|
||||||
// Adds annotations to the JSON schema reading from the annotation files. | ||||||
// More details https://json-schema.org/understanding-json-schema/reference/annotations | ||||||
func newAnnotationHandler(sources []string) (*annotationHandler, error) { | ||||||
prev := dyn.NilValue | ||||||
for _, path := range sources { | ||||||
b, err := os.ReadFile(path) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
generated, err := yamlloader.LoadYAML(path, bytes.NewBuffer(b)) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
prev, err = merge.Merge(prev, generated) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
} | ||||||
|
||||||
var data annotationFile | ||||||
|
||||||
err := convert.ToTyped(&data, prev) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
|
||||||
d := &annotationHandler{} | ||||||
d.ref = data | ||||||
d.empty = annotationFile{} | ||||||
return d, nil | ||||||
} | ||||||
|
||||||
func (d *annotationHandler) addAnnotations(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { | ||||||
refPath := getPath(typ) | ||||||
shouldHandle := strings.HasPrefix(refPath, "github.com") | ||||||
if !shouldHandle { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which types don't have this prefix? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some primitive types like |
||||||
return s | ||||||
} | ||||||
|
||||||
annotations := d.ref[refPath] | ||||||
if annotations == nil { | ||||||
annotations = map[string]annotation{} | ||||||
} | ||||||
|
||||||
rootTypeAnnotation, ok := annotations[RootTypeKey] | ||||||
if ok { | ||||||
assingAnnotation(&s, rootTypeAnnotation) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo
Suggested change
|
||||||
} | ||||||
|
||||||
for k, v := range s.Properties { | ||||||
item, ok := annotations[k] | ||||||
if !ok { | ||||||
item = annotation{} | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value of the map is a value type, so The |
||||||
if item.Description == "" { | ||||||
item.Description = Placeholder | ||||||
|
||||||
emptyAnnotations := d.empty[refPath] | ||||||
if emptyAnnotations == nil { | ||||||
emptyAnnotations = map[string]annotation{} | ||||||
d.empty[refPath] = emptyAnnotations | ||||||
} | ||||||
emptyAnnotations[k] = item | ||||||
} | ||||||
assingAnnotation(v, item) | ||||||
} | ||||||
return s | ||||||
} | ||||||
|
||||||
// Adds empty annotations with placeholders to the annotation file | ||||||
func (d *annotationHandler) sync(outputPath string) error { | ||||||
existingFile, err := os.ReadFile(outputPath) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
|
||||||
missingAnnotations, err := yaml.Marshal(d.empty) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
err = saveYamlWithStyle(outputPath, existingFile, missingAnnotations) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
return nil | ||||||
} | ||||||
|
||||||
func getPath(typ reflect.Type) string { | ||||||
return typ.PkgPath() + "." + typ.Name() | ||||||
} | ||||||
|
||||||
func assingAnnotation(s *jsonschema.Schema, a annotation) { | ||||||
if a.Description != Placeholder { | ||||||
s.Description = a.Description | ||||||
} | ||||||
|
||||||
if a.Default != nil { | ||||||
s.Default = a.Default | ||||||
} | ||||||
s.MarkdownDescription = a.MarkdownDescription | ||||||
s.Title = a.Title | ||||||
s.Enum = a.Enum | ||||||
} | ||||||
|
||||||
func saveYamlWithStyle(outputPath string, input []byte, overrides []byte) error { | ||||||
inputDyn, err := yamlloader.LoadYAML("", bytes.NewBuffer(input)) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
if len(overrides) != 0 { | ||||||
overrideDyn, err := yamlloader.LoadYAML("", bytes.NewBuffer(overrides)) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
inputDyn, err = merge.Merge(inputDyn, overrideDyn) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
} | ||||||
|
||||||
style := map[string]yaml3.Style{} | ||||||
file, _ := inputDyn.AsMap() | ||||||
for _, v := range file.Keys() { | ||||||
style[v.MustString()] = yaml3.LiteralStyle | ||||||
} | ||||||
|
||||||
saver := yamlsaver.NewSaverWithStyle(style) | ||||||
err = saver.SaveAsYAML(file, outputPath, true) | ||||||
if err != nil { | ||||||
return err | ||||||
} | ||||||
return nil | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need the path to itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I passed this as argument to avoid hardcoding relative paths in Go (to keep an ability to execute both from
make schema
and from other places like Run from IDE). Other possible option to keep portability is to useembed
but I also need write operations so it seems like not an option in my case