mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-13 12:27:59 +00:00
Separate library from CLI concerns: - Add Resolver callback type with Decision/Response structs for all interactive decisions (map/struct, type name, tuple/list, shape unification, shape naming, name collision) - Move terminal I/O (Prompter) from library to cmd/jsonpaths - Add public API: New(), ParseFormat(), Generate(), AutoGenerate() - Add Format type with aliases (ts, py, json-paths, etc.) - Fix godoc comments to match exported function names - Update tests to use scriptedResolver instead of Prompter internals - Update doc.go and README with current API
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package jsontypes
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
)
|
|
|
|
// GenerateTypedef converts formatted flat paths into a JSON Typedef (RFC 8927) document.
|
|
func GenerateTypedef(paths []string) string {
|
|
types, _ := buildGoTypes(paths)
|
|
|
|
typeMap := make(map[string]goType)
|
|
for _, t := range types {
|
|
typeMap[t.name] = t
|
|
}
|
|
|
|
// The first type is the root
|
|
if len(types) == 0 {
|
|
return "{}\n"
|
|
}
|
|
|
|
root := types[0]
|
|
defs := make(map[string]any)
|
|
result := structToJTD(root, typeMap, defs)
|
|
|
|
if len(defs) > 0 {
|
|
result["definitions"] = defs
|
|
}
|
|
|
|
data, _ := json.MarshalIndent(result, "", " ")
|
|
return string(data) + "\n"
|
|
}
|
|
|
|
// structToJTD converts a goType to a JTD schema object.
|
|
func structToJTD(t goType, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
props := make(map[string]any)
|
|
optProps := make(map[string]any)
|
|
|
|
for _, f := range t.fields {
|
|
schema := goTypeToJTD(f.goType, f.optional, typeMap, defs)
|
|
if f.optional {
|
|
optProps[f.jsonName] = schema
|
|
} else {
|
|
props[f.jsonName] = schema
|
|
}
|
|
}
|
|
|
|
result := make(map[string]any)
|
|
if len(props) > 0 {
|
|
result["properties"] = props
|
|
} else if len(optProps) > 0 {
|
|
// JTD requires "properties" if "optionalProperties" is present
|
|
result["properties"] = map[string]any{}
|
|
}
|
|
if len(optProps) > 0 {
|
|
result["optionalProperties"] = optProps
|
|
}
|
|
return result
|
|
}
|
|
|
|
// goTypeToJTD converts a Go type string to a JTD schema.
|
|
func goTypeToJTD(goTyp string, nullable bool, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
result := goTypeToJTDInner(goTyp, typeMap, defs)
|
|
if nullable {
|
|
result["nullable"] = true
|
|
}
|
|
return result
|
|
}
|
|
|
|
func goTypeToJTDInner(goTyp string, typeMap map[string]goType, defs map[string]any) map[string]any {
|
|
// Strip pointer
|
|
goTyp = strings.TrimPrefix(goTyp, "*")
|
|
|
|
// Slice
|
|
if strings.HasPrefix(goTyp, "[]") {
|
|
elemType := goTyp[2:]
|
|
return map[string]any{
|
|
"elements": goTypeToJTDInner(elemType, typeMap, defs),
|
|
}
|
|
}
|
|
|
|
// Map
|
|
if strings.HasPrefix(goTyp, "map[string]") {
|
|
valType := goTyp[11:]
|
|
return map[string]any{
|
|
"values": goTypeToJTDInner(valType, typeMap, defs),
|
|
}
|
|
}
|
|
|
|
// Primitives
|
|
switch goTyp {
|
|
case "string":
|
|
return map[string]any{"type": "string"}
|
|
case "int64":
|
|
// JTD (RFC 8927) has no int64 type; int32 is the largest integer type available.
|
|
return map[string]any{"type": "int32"}
|
|
case "float64":
|
|
return map[string]any{"type": "float64"}
|
|
case "bool":
|
|
return map[string]any{"type": "boolean"}
|
|
case "any":
|
|
return map[string]any{}
|
|
}
|
|
|
|
// Named struct — emit as ref, add to definitions if not already there
|
|
if t, ok := typeMap[goTyp]; ok {
|
|
if _, exists := defs[goTyp]; !exists {
|
|
// Add placeholder to prevent infinite recursion
|
|
defs[goTyp] = nil
|
|
defs[goTyp] = structToJTD(t, typeMap, defs)
|
|
}
|
|
return map[string]any{"ref": goTyp}
|
|
}
|
|
|
|
// Unknown type
|
|
return map[string]any{}
|
|
}
|