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

support recursive arrays / arrays of references (plus a bunch of other changes) #55

Merged
merged 13 commits into from
Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/schema-generate
.idea
test/*_gen
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: go

go_import_path: github.com/a-h/generate

script:
- make codecheck
- make test
19 changes: 15 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PKG := github.com/a-h/generate
PKG := .
CMD := $(PKG)/cmd/schema-generate
BIN := schema-generate

Expand All @@ -8,22 +8,33 @@ BIN := schema-generate

all: clean $(BIN)

$(BIN):
$(BIN): generator.go jsonschema.go cmd/schema-generate/main.go
@echo "+ Building $@"
CGO_ENABLED="0" go build -v -o $@ $(CMD)

clean:
@echo "+ Cleaning $(PKG)"
go clean -i $(PKG)/...
rm -f $(BIN)
rm -rf test/*_gen

# Test

# generate sources
JSON := $(wildcard test/*.json)
GENERATED_SOURCE := $(patsubst %.json,%_gen/generated.go,$(JSON))
test/%_gen/generated.go: test/%.json
@echo "\n+ Generating code for $@"
@D=$(shell echo $^ | sed 's/.json/_gen/'); \
[ ! -d $$D ] && mkdir -p $$D || true
./schema-generate -o $@ -p $(shell echo $^ | sed 's/test\///; s/.json//') $^

.PHONY: test codecheck fmt lint vet

test:
@echo "+ Executing tests for $(PKG)"
test: $(BIN) $(GENERATED_SOURCE)
@echo "\n+ Executing tests for $(PKG)"
go test -v -race -cover $(PKG)/...


codecheck: fmt lint vet

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generate

Generates Go (golang) Structs from JSON schema.
Generates Go (golang) Structs and Validation code from JSON schema.

# Requirements

Expand Down
136 changes: 7 additions & 129 deletions cmd/schema-generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,11 @@
package main

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

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

var (
Expand Down Expand Up @@ -42,42 +36,18 @@ func main() {
os.Exit(1)
}

schemas := make([]*jsonschema.Schema, len(inputFiles))
for i, file := range inputFiles {
b, err := ioutil.ReadFile(file)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to read the input file with error ", err)
return
}

schemas[i], err = jsonschema.Parse(string(b))
if err != nil {
if jsonError, ok := err.(*json.SyntaxError); ok {
line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset))
fmt.Fprintf(os.Stderr, "Cannot parse JSON schema due to a syntax error at %s line %d, character %d: %v\n", file, line, character, jsonError.Error())
if lcErr != nil {
fmt.Fprintf(os.Stderr, "Couldn't find the line and character position of the error due to error %v\n", lcErr)
}
return
}
if jsonError, ok := err.(*json.UnmarshalTypeError); ok {
line, character, lcErr := lineAndCharacter(b, int(jsonError.Offset))
fmt.Fprintf(os.Stderr, "The JSON type '%v' cannot be converted into the Go '%v' type on struct '%s', field '%v'. See input file %s line %d, character %d\n", jsonError.Value, jsonError.Type.Name(), jsonError.Struct, jsonError.Field, file, line, character)
if lcErr != nil {
fmt.Fprintf(os.Stderr, "Couldn't find the line and character position of the error due to error %v\n", lcErr)
}
return
}
fmt.Fprintf(os.Stderr, "Failed to parse the input JSON schema file %s with error %v\n", file, err)
return
}
schemas, err := generate.ReadInputFiles(inputFiles)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}

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

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

var w io.Writer = os.Stdout
Expand All @@ -91,97 +61,5 @@ func main() {
}
}

output(w, structs, aliases)
}

func lineAndCharacter(bytes []byte, offset int) (line int, character int, err error) {
lf := byte(0x0A)

if offset > len(bytes) {
return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes))
}

// Humans tend to count from 1.
line = 1

for i, b := range bytes {
if b == lf {
line++
character = 0
}
character++
if i == offset {
return line, character, nil
}
}

return 0, 0, fmt.Errorf("couldn't find offset %d in %d bytes", offset, len(bytes))
}

func getOrderedFieldNames(m map[string]generate.Field) []string {
keys := make([]string, len(m))
idx := 0
for k := range m {
keys[idx] = k
idx++
}
sort.Strings(keys)
return keys
}

func getOrderedStructNames(m map[string]generate.Struct) []string {
keys := make([]string, len(m))
idx := 0
for k := range m {
keys[idx] = k
idx++
}
sort.Strings(keys)
return keys
}

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]

fmt.Fprintln(w, "")
outputNameAndDescriptionComment(s.Name, s.Description, w)
fmt.Fprintf(w, "type %s struct {\n", s.Name)

for _, fieldKey := range getOrderedFieldNames(s.Fields) {
f := s.Fields[fieldKey]

// Only apply omitempty if the field is not required.
omitempty := ",omitempty"
if f.Required {
omitempty = ""
}

fmt.Fprintf(w, " %s %s `json:\"%s%s\"`\n", f.Name, f.Type, f.JSONName, omitempty)
}

fmt.Fprintln(w, "}")
}
}

func outputNameAndDescriptionComment(name, description string, w io.Writer) {
if strings.Index(description, "\n") == -1 {
fmt.Fprintf(w, "// %s %s\n", name, description)
return
}

dl := strings.Split(description, "\n")
fmt.Fprintf(w, "// %s %s\n", name, strings.Join(dl, "\n// "))
generate.Output(w, g, *p)
}
115 changes: 0 additions & 115 deletions cmd/schema-generate/main_test.go
Original file line number Diff line number Diff line change
@@ -1,117 +1,2 @@
package main

import (
"bytes"
"io"
"io/ioutil"
"reflect"
"strings"
"testing"

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

func TestThatFieldNamesAreOrdered(t *testing.T) {
m := map[string]generate.Field{
"z": {},
"b": {},
}

actual := getOrderedFieldNames(m)
expected := []string{"b", "z"}

if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ","))
}
}

func TestThatStructNamesAreOrdered(t *testing.T) {
m := map[string]generate.Struct{
"c": {},
"b": {},
"a": {},
}

actual := getOrderedStructNames(m)
expected := []string{"a", "b", "c"}

if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected %s and actual %s should match in order", strings.Join(expected, ", "), strings.Join(actual, ","))
}
}

func TestThatThePackageCanBeSet(t *testing.T) {
pkg := "testpackage"
p = &pkg

r, w := io.Pipe()

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

lr := io.LimitedReader{R: r, N: 72}
bs, _ := ioutil.ReadAll(&lr)
output := bytes.NewBuffer(bs).String()

want := `// Code generated by schema-generate. DO NOT EDIT.

package testpackage
`
if output != want {
t.Error("Unexpected package declaration: ", output)
}
}

func TestLineAndCharacterFromOffset(t *testing.T) {
tests := []struct {
In []byte
Offset int
ExpectedLine int
ExpectedCharacter int
ExpectedError bool
}{
{
In: []byte("Line 1\nLine 2"),
Offset: 6,
ExpectedLine: 2,
ExpectedCharacter: 1,
},
{
In: []byte("Line 1\r\nLine 2"),
Offset: 7,
ExpectedLine: 2,
ExpectedCharacter: 1,
},
{
In: []byte("Line 1\nLine 2"),
Offset: 0,
ExpectedLine: 1,
ExpectedCharacter: 1,
},
{
In: []byte("Line 1\nLine 2"),
Offset: 200,
ExpectedLine: 0,
ExpectedCharacter: 0,
ExpectedError: true,
},
{
In: []byte("Line 1\nLine 2"),
Offset: -1,
ExpectedLine: 0,
ExpectedCharacter: 0,
ExpectedError: true,
},
}

for _, test := range tests {
actualLine, actualCharacter, err := lineAndCharacter(test.In, test.Offset)
if err != nil && !test.ExpectedError {
t.Errorf("Unexpected error for input %s at offset %d: %v", test.In, test.Offset, err)
continue
}

if actualLine != test.ExpectedLine || actualCharacter != test.ExpectedCharacter {
t.Errorf("For '%s' at offset %d, expected %d:%d, but got %d:%d", test.In, test.Offset, test.ExpectedLine, test.ExpectedCharacter, actualLine, actualCharacter)
}
}
}
Loading