Skip to content
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

Root types aliases #40

Merged
merged 1 commit into from
Apr 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions cmd/schema-generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"sort"

"encoding/json"

"github.com/a-h/generate"
"github.com/a-h/generate/jsonschema"
)
Expand Down Expand Up @@ -74,17 +73,14 @@ func main() {

g := generate.New(schemas...)

structs, err := g.CreateStructs()

structs, aliases, err := g.CreateTypes()
if err != nil {
fmt.Fprintln(os.Stderr, "Failure generating structs: ", err)
}

var w io.Writer
var w io.Writer = os.Stdout
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


if *o == "" {
w = os.Stdout
} else {
if *o != "" {
w, err = os.Create(*o)

if err != nil {
Expand All @@ -93,7 +89,7 @@ func main() {
}
}

output(w, structs)
output(w, structs, aliases)
}

func lineAndCharacter(bytes []byte, offset int) (line int, character int, err error) {
Expand Down Expand Up @@ -142,11 +138,19 @@ func getOrderedStructNames(m map[string]generate.Struct) []string {
return keys
}

func output(w io.Writer, structs map[string]generate.Struct) {
func output(w io.Writer, structs map[string]generate.Struct, aliases map[string]generate.Field) {
fmt.Fprintln(w, "// Code generated by schema-generate. DO NOT EDIT.")
fmt.Fprintln(w)
fmt.Fprintf(w, "package %v\n", *p)

for _, k := range getOrderedFieldNames(aliases) {
a := aliases[k]

fmt.Fprintln(w, "")
fmt.Fprintf(w, "// %s\n", a.Name)
fmt.Fprintf(w, "type %s %s\n", a.Name, a.Type)
}

for _, k := range getOrderedStructNames(structs) {
s := structs[k]

Expand Down
2 changes: 1 addition & 1 deletion cmd/schema-generate/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestThatThePackageCanBeSet(t *testing.T) {

r, w := io.Pipe()

go output(w, make(map[string]generate.Struct))
go output(w, make(map[string]generate.Struct), make(map[string]generate.Field))

lr := io.LimitedReader{R: r, N: 72}
bs, _ := ioutil.ReadAll(&lr)
Expand Down
125 changes: 90 additions & 35 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package generate

import (
"bytes"
"errors"
"fmt"
"net/url"
"path"
"sort"
"strings"

"errors"

"github.com/a-h/generate/jsonschema"
)

Expand All @@ -26,14 +25,14 @@ func New(schemas ...*jsonschema.Schema) *Generator {
}
}

// CreateStructs creates structs from the JSON schemas, keyed by the golang name.
func (g *Generator) CreateStructs() (structs map[string]Struct, err error) {
// CreateTypes creates types from the JSON schemas, keyed by the golang name.
func (g *Generator) CreateTypes() (structs map[string]Struct, aliases map[string]Field, err error) {
schemaIDs := make([]*url.URL, len(g.schemas))
for i, schema := range g.schemas {
if schema.ID() != "" {
schemaIDs[i], err = url.Parse(schema.ID())
if err != nil {
return nil, err
return
}
}
}
Expand All @@ -52,46 +51,91 @@ func (g *Generator) CreateStructs() (structs map[string]Struct, err error) {
}

structs = make(map[string]Struct)
aliases = make(map[string]Field)
errs := []error{}

for _, typeKey := range getOrderedKeyNamesFromSchemaMap(types) {
v := types[typeKey]

typeKeyURI, err := url.Parse(typeKey)
if err != nil {
errs = append(errs, err)
}
fields, err := getFields(typeKeyURI, v.Properties, types, v.Required)
if v.TypeValue == "object" || v.TypeValue == nil {
s, errtype := createStruct(typeKey, v, types)
if errtype != nil {
errs = append(errs, errtype...)
}

if err != nil {
errs = append(errs, err)
}
if _, ok := structs[s.Name]; ok {
errs = append(errs, errors.New("Duplicate struct name : "+s.Name))
}

structName := getStructName(typeKeyURI, v, 1)
structs[s.Name] = s
} else {
a, errtype := createAlias(typeKey, v, types)
if errtype != nil {
errs = append(errs, errtype...)
}

if err != nil {
errs = append(errs, err)
aliases[a.Name] = a
}
}

s := Struct{
ID: typeKey,
Name: structName,
Description: v.Description,
Fields: fields,
}
if len(errs) > 0 {
err = errors.New(joinErrors(errs))
}
return
}

if _, ok := structs[s.Name]; ok {
errs = append(errs, errors.New("Duplicate struct name : "+s.Name))
}
// createStruct creates a struct type from the JSON schema.
func createStruct(typeKey string, schema *jsonschema.Schema, types map[string]*jsonschema.Schema) (s Struct, errs []error) {
typeKeyURI, err := url.Parse(typeKey)
if err != nil {
errs = append(errs, err)
}

structs[s.Name] = s
fields, err := getFields(typeKeyURI, schema.Properties, types, schema.Required)
if err != nil {
errs = append(errs, err)
}

if len(errs) > 0 {
return structs, errors.New(joinErrors(errs))
structName := getTypeName(typeKeyURI, schema, 1)
if err != nil {
errs = append(errs, err)
}

s = Struct{
ID: typeKey,
Name: structName,
Description: schema.Description,
Fields: fields,
}

return structs, nil
return
}

// createAlias creates a simple alias type from the JSON schema.
func createAlias(typeKey string, schema *jsonschema.Schema, types map[string]*jsonschema.Schema) (a Field, errs []error) {
typeKeyURI, err := url.Parse(typeKey)
if err != nil {
errs = append(errs, err)
}

aliasName := getTypeName(typeKeyURI, schema, 1)
if err != nil {
errs = append(errs, err)
}

tn, err := getTypeForField(typeKeyURI, typeKey, aliasName, schema, types, true)
if err != nil {
errs = append(errs, err)
}

a = Field{
Name: aliasName,
JSONName: "",
Type: tn,
Required: false,
}

return
}

func joinErrors(errs []error) string {
Expand Down Expand Up @@ -190,7 +234,7 @@ func getTypeForField(parentTypeKey *url.URL, fieldName string, fieldGoName strin
ref = parentTypeKey.ResolveReference(ref)

if t, ok := types[ref.String()]; ok {
sn := getStructName(ref, t, 1)
sn := getTypeName(ref, t, 1)

majorType = "object"
subType = sn
Expand All @@ -200,7 +244,7 @@ func getTypeForField(parentTypeKey *url.URL, fieldName string, fieldGoName strin
}

// Look up any embedded types.
if subType == "" && majorType == "object" {
if subType == "" && (majorType == "object" || majorType == "") {
if len(fieldSchema.Properties) == 0 && len(fieldSchema.AdditionalProperties) > 0 {
if len(fieldSchema.AdditionalProperties) == 1 {
sn, _ := getTypeForField(parentTypeKey, fieldName, fieldGoName,
Expand All @@ -213,8 +257,14 @@ func getTypeForField(parentTypeKey *url.URL, fieldName string, fieldGoName strin
}
} else {
ref := joinURLFragmentPath(parentTypeKey, "properties/"+fieldName)

// Root schema without properties, try array item instead
if _, ok := types[ref.String()]; !ok && isRootSchemaKey(parentTypeKey) {
ref = joinURLFragmentPath(parentTypeKey, "arrayitems")
}

if parentType, ok := types[ref.String()]; ok {
sn := getStructName(ref, parentType, 1)
sn := getTypeName(ref, parentType, 1)
subType = sn
} else {
subType = "undefined"
Expand All @@ -239,6 +289,11 @@ func getTypeForField(parentTypeKey *url.URL, fieldName string, fieldGoName strin
return name, nil
}

// isRootSchemaKey returns whether a given type key references the root schema.
func isRootSchemaKey(typeKey *url.URL) bool {
return typeKey.Fragment == ""
}

// joinURLFragmentPath joins elem onto u.Fragment, adding a separating slash.
func joinURLFragmentPath(base *url.URL, elem string) *url.URL {
url := *base
Expand Down Expand Up @@ -278,14 +333,14 @@ func getPrimitiveTypeName(schemaType string, subType string, pointer bool) (name
schemaType, subType)
}

// getStructName makes a golang struct name from an input reference in the form of #/definitions/address
// getTypeName makes a golang type name from an input reference in the form of #/definitions/address
// The parts refers to the number of segments from the end to take as the name.
func getStructName(reference *url.URL, structType *jsonschema.Schema, n int) string {
func getTypeName(reference *url.URL, structType *jsonschema.Schema, n int) string {
if len(structType.Title) > 0 {
return getGolangName(structType.Title)
}

if reference.Fragment == "" {
if isRootSchemaKey(reference) {
rootName := structType.Title

if rootName == "" {
Expand Down
Loading