cmd/gen-jsonschema/main.go (85 lines of code) (raw):

package main import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "slices" "strings" "github.com/Azure/dalec" "github.com/atombender/go-jsonschema/pkg/schemas" "github.com/invopop/jsonschema" ) func main() { var r jsonschema.Reflector if err := r.AddGoComments("github.com/Azure/dalec", "./"); err != nil { panic(err) } schema := r.Reflect(&dalec.Spec{}) if schema.PatternProperties == nil { schema.PatternProperties = make(map[string]*jsonschema.Schema) } schema.PatternProperties["^x-"] = &jsonschema.Schema{} dt, err := json.Marshal(schema) if err != nil { panic(err) } // The above library used is good for generating the schema from the go types, // but it doesn't give us everything we need to make manipulations to the schema // since the data is not represented in go correctly. // So we convert the schema to JSON and then back to another go type that is more // suitable for manipulation. // // Specifically, the problem with the above library is that the `Type` parameter // is a string, but it should be a []string. // Both are apparently(?) valid jsonschema, but the latter is what we need to // fixup the schema to allow null values and other things. schema2, err := schemas.FromJSONReader(bytes.NewReader(dt)) if err != nil { panic(err) } const ( specKey = "Spec" argsKey = "args" buildKey = "build" ) spec := schema2.Definitions[specKey] spec.Properties[argsKey].AdditionalProperties.Type = append(spec.Properties[argsKey].AdditionalProperties.Type, "integer") build := spec.Properties[buildKey] buildType := strings.TrimPrefix(build.Ref, "#/$defs/") build = schema2.Definitions[buildType] build.Properties["env"].Type = append(build.Properties["env"].Type, "integer") for _, v := range schema2.Definitions { setObjectAllowNull(v) } dt, err = json.MarshalIndent(schema2, "", "\t") if err != nil { panic(err) } if len(os.Args) > 1 { if err := os.MkdirAll(filepath.Dir(os.Args[1]), 0755); err != nil { panic(err) } if err := os.WriteFile(os.Args[1], dt, 0644); err != nil { panic(err) } return } fmt.Println(string(dt)) } func setObjectAllowNull(t *schemas.Type) { if t == nil { panic("nil type") } if t.AdditionalProperties != nil { setObjectAllowNull(t.AdditionalProperties) } for k, v := range t.Properties { if slices.Contains(t.Required, k) { continue } setObjectAllowNull(v) t.Properties[k] = v } ok := slices.ContainsFunc(t.Type, func(v string) bool { if v == "null" { // Already allows null, nothing to do. return false } return v == "object" || v == "string" }) if !ok { return } t.Type = append(t.Type, "null") }