omissis/go-jsonschema

View on GitHub
pkg/codegen/model.go

Summary

Maintainability
A
35 mins
Test Coverage
package codegen

import (
    "sort"
    "strings"

    "golang.org/x/exp/slices"

    "github.com/atombender/go-jsonschema/pkg/schemas"

    "github.com/sanity-io/litter"
)

type Decl interface {
    Generate(out *Emitter)
}

type Named interface {
    Decl
    GetName() string
}

type File struct {
    FileName string
    Package  Package
}

func (p *File) Generate(out *Emitter) {
    out.Comment("Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.")
    out.Newline()
    p.Package.Generate(out)
}

// Package is a "package <name>; <body>".
type Package struct {
    QualifiedName string
    Comment       string
    Decls         []Decl
    Imports       []Import
}

func (p *Package) AddDecl(t Decl) {
    p.Decls = append(p.Decls, t)
}

func (p *Package) AddImport(qualifiedName, alias string) {
    if !p.hasImport(qualifiedName) {
        p.Imports = append(p.Imports, Import{
            QualifiedName: qualifiedName,
            Name:          alias,
        })
    }
}

func (p *Package) hasImport(q string) bool {
    for _, i := range p.Imports {
        if i.QualifiedName == q {
            return true
        }
    }

    return false
}

func (p *Package) Name() string {
    s := p.QualifiedName
    if i := strings.LastIndex(s, "/"); i != -1 && i < len(s)-1 {
        return s[i+1:]
    }

    return s
}

func (p *Package) Generate(out *Emitter) {
    out.Comment(p.Comment)
    out.Printlnf("package %s", p.Name())

    if len(p.Imports) > 0 {
        slices.SortStableFunc(p.Imports, func(i, j Import) int {
            if i.QualifiedName < j.QualifiedName {
                return -1
            }

            if i.QualifiedName > j.QualifiedName {
                return 1
            }

            return 0
        })

        for _, i := range p.Imports {
            i.Generate(out)
        }
    }

    out.Newline()

    sorted := make([]Decl, len(p.Decls))
    copy(sorted, p.Decls)
    sort.Slice(sorted, func(i, j int) bool {
        if a, ok := sorted[i].(Named); ok {
            if b, ok := sorted[j].(Named); ok {
                return schemas.CleanNameForSorting(a.GetName()) < schemas.CleanNameForSorting(b.GetName())
            }
        }

        return false
    })

    for i, t := range sorted {
        if i > 0 {
            out.Newline()
        }

        t.Generate(out)
    }
}

// Var is a "var <name> = <value>".
type Var struct {
    Type  Type
    Name  string
    Value interface{}
}

func (v *Var) GetName() string {
    return v.Name
}

func (v *Var) Generate(out *Emitter) {
    out.Printf("var %s ", v.Name)

    if v.Type != nil {
        v.Type.Generate(out)
    }

    out.Printf(" = %s", litter.Sdump(v.Value))
}

// Constant is a "const <name> = <value>".
type Constant struct {
    Type  Type
    Name  string
    Value interface{}
}

func (c *Constant) GetName() string {
    return c.Name
}

func (c *Constant) Generate(out *Emitter) {
    out.Printf("const %s ", c.Name)

    if c.Type != nil {
        c.Type.Generate(out)
    }

    out.Printf(" = %s", litter.Sdump(c.Value))
}

// Fragment is an arbitrary piece of code.
type Fragment func(*Emitter)

func (f Fragment) Generate(out *Emitter) {
    f(out)
}

// Method defines a method and how to generate it.
type Method struct {
    Impl func(*Emitter)
    Name string
}

func (m *Method) GetName() string {
    return m.Name
}

func (m *Method) Generate(out *Emitter) {
    out.Newline()
    m.Impl(out)
    out.Newline()
}

// Import is a "type <name> = <definition>".
type Import struct {
    Name          string
    QualifiedName string
}

func (i *Import) Generate(out *Emitter) {
    if i.Name != "" {
        out.Printlnf("import %s %q", i.Name, i.QualifiedName)
    } else {
        out.Printlnf("import %q", i.QualifiedName)
    }
}

// TypeDecl is a "type <name> = <definition>".
type TypeDecl struct {
    Name    string
    Type    Type
    Comment string
}

func (td *TypeDecl) GetName() string {
    return td.Name
}

func (td *TypeDecl) Generate(out *Emitter) {
    out.Comment(td.Comment)
    out.Printf("type %s ", td.Name)
    td.Type.Generate(out)
    out.Newline()
}

type Type interface {
    Decl
    IsNillable() bool
}

type PointerType struct {
    Type Type
}

func (PointerType) IsNillable() bool { return true }

func (p PointerType) Generate(out *Emitter) {
    out.Printf("*")
    p.Type.Generate(out)
}

type ArrayType struct {
    Type Type
}

func (ArrayType) IsNillable() bool { return true }

func (a ArrayType) Generate(out *Emitter) {
    out.Printf("[]")
    a.Type.Generate(out)
}

type NamedType struct {
    Package *Package
    Decl    *TypeDecl
}

func (t NamedType) GetName() string {
    return t.Decl.Name
}

func (t NamedType) IsNillable() bool {
    return t.Decl.Type != nil && t.Decl.Type.IsNillable()
}

func (t NamedType) Generate(out *Emitter) {
    if t.Package != nil {
        out.Printf("%s", t.Package.Name())
        out.Printf(".")
    }

    out.Printf("%s", t.Decl.Name)
}

type PrimitiveType struct {
    Type string
}

func (PrimitiveType) IsNillable() bool { return false }

func (p PrimitiveType) Generate(out *Emitter) {
    out.Printf("%s", p.Type)
}

type CustomNameType struct {
    Type     string
    Nillable bool
}

func (p CustomNameType) IsNillable() bool { return p.Nillable }

func (p CustomNameType) Generate(out *Emitter) {
    out.Printf("%s", p.Type)
}

type MapType struct {
    KeyType, ValueType Type
}

func (MapType) IsNillable() bool { return true }

func (p MapType) Generate(out *Emitter) {
    out.Printf("map[")
    p.KeyType.Generate(out)
    out.Printf("]")
    p.ValueType.Generate(out)
}

type EmptyInterfaceType struct{}

func (EmptyInterfaceType) IsNillable() bool { return true }

func (EmptyInterfaceType) Generate(out *Emitter) {
    out.Printf("interface{}")
}

type NullType struct{}

func (NullType) IsNillable() bool { return true }

func (NullType) Generate(out *Emitter) {
    out.Printf("interface{}")
}

type StructType struct {
    Fields             []StructField
    RequiredJSONFields []string
}

func (StructType) IsNillable() bool { return false }

func (s *StructType) AddField(f StructField) {
    s.Fields = append(s.Fields, f)
}

func (s *StructType) Generate(out *Emitter) {
    out.Printlnf("struct {")
    out.Indent(1)

    i := 0

    for _, f := range s.Fields {
        if i > 0 {
            out.Newline()
        }

        f.Generate(out)
        out.Newline()

        i++
    }

    out.Indent(-1)
    out.Printf("}")
}

type StructField struct {
    Name         string
    Type         Type
    Comment      string
    Tags         string
    JSONName     string
    DefaultValue interface{}
    SchemaType   *schemas.Type
}

func (f *StructField) GetName() string {
    return f.Name
}

func (f *StructField) Generate(out *Emitter) {
    out.Comment(f.Comment)
    out.Printf("%s ", f.Name)
    f.Type.Generate(out)

    if f.Tags != "" {
        out.Printf(" `%s`", f.Tags)
    }
}