Skip to content

Commit

Permalink
rearranged code slightly so it can be reused in an external project
Browse files Browse the repository at this point in the history
  • Loading branch information
mwlazlo committed Oct 16, 2018
1 parent c19cf61 commit 57add5d
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 282 deletions.
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
4 changes: 2 additions & 2 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,7 +8,7 @@ BIN := schema-generate

all: clean $(BIN)

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

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
84 changes: 5 additions & 79 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"

"github.com/a-h/generate"
"github.com/a-h/generate/jsonschema"
"net/url"
"path"
)

var (
Expand Down Expand Up @@ -42,51 +36,15 @@ 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
}

abPath, err := Abs(file)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to normalise input path with error ", err)
return
}

fileURI := url.URL{
Scheme: "file",
Path: abPath,
}

schemas[i], err = jsonschema.Parse(string(b), &fileURI)
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...)

err := g.CreateTypes()
err = g.CreateTypes()
if err != nil {
fmt.Fprintln(os.Stderr, "Failure generating structs: ", err)
os.Exit(1)
Expand All @@ -105,35 +63,3 @@ func main() {

generate.Output(w, g, *p)
}

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 Abs(name string) (string, error) {
if path.IsAbs(name) {
return name, nil
}
wd, err := os.Getwd()
return path.Join(wd, name), err
}
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)
}
}
}
25 changes: 12 additions & 13 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"unicode"
"errors"
"sort"
"github.com/a-h/generate/jsonschema"
)

// Generator will produce structs from the JSON schema.
type Generator struct {
schemas []*jsonschema.Schema
resolver *jsonschema.RefResolver
schemas []*Schema
resolver *RefResolver
Structs map[string]Struct
Aliases map[string]Field
// cache for reference types; k=url v=type
Expand All @@ -22,10 +21,10 @@ type Generator struct {
}

// New creates an instance of a generator which will produce structs.
func New(schemas ...*jsonschema.Schema) *Generator {
func New(schemas ...*Schema) *Generator {
return &Generator{
schemas: schemas,
resolver: jsonschema.NewRefResolver(schemas),
resolver: NewRefResolver(schemas),
Structs: make(map[string]Struct),
Aliases: make(map[string]Field),
refs: make(map[string]string),
Expand Down Expand Up @@ -62,7 +61,7 @@ func (g *Generator) CreateTypes() (err error) {
}

// process a block of definitions
func (g *Generator) processDefinitions(schema *jsonschema.Schema) error {
func (g *Generator) processDefinitions(schema *Schema) error {
for key, subSchema := range schema.Definitions {
if _, err := g.processSchema(getGolangName(key), subSchema); err != nil {
return err
Expand All @@ -72,7 +71,7 @@ func (g *Generator) processDefinitions(schema *jsonschema.Schema) error {
}

// process a reference string
func (g *Generator) processReference(schema *jsonschema.Schema) (string, error) {
func (g *Generator) processReference(schema *Schema) (string, error) {
schemaPath := g.resolver.GetPath(schema)
if schema.Reference == "" {
return "", errors.New("processReference empty reference: "+ schemaPath)
Expand All @@ -95,7 +94,7 @@ func (g *Generator) processReference(schema *jsonschema.Schema) (string, error)
}

// returns the type refered to by schema after resolving all dependencies
func (g *Generator) processSchema(schemaName string, schema *jsonschema.Schema) (typ string, err error) {
func (g *Generator) processSchema(schemaName string, schema *Schema) (typ string, err error) {
if len(schema.Definitions) > 0 {
g.processDefinitions(schema)
}
Expand Down Expand Up @@ -146,7 +145,7 @@ func (g *Generator) processSchema(schemaName string, schema *jsonschema.Schema)

// name: name of this array, usually the js key
// schema: items element
func (g *Generator) processArray(name string, schema *jsonschema.Schema) (typeStr string, err error) {
func (g *Generator) processArray(name string, schema *Schema) (typeStr string, err error) {
if schema.Items != nil {
// subType: fallback name in case this array contains inline object without a title
subName := g.getSchemaName(name + "Items", schema.Items)
Expand Down Expand Up @@ -177,7 +176,7 @@ func (g *Generator) processArray(name string, schema *jsonschema.Schema) (typeSt
// name: name of the struct (calculated by caller)
// schema: detail incl properties & child objects
// returns: generated type
func (g *Generator) processObject(name string, schema *jsonschema.Schema) (typ string, err error) {
func (g *Generator) processObject(name string, schema *Schema) (typ string, err error) {
strct := Struct{
ID: schema.ID(),
Name: name,
Expand Down Expand Up @@ -209,7 +208,7 @@ func (g *Generator) processObject(name string, schema *jsonschema.Schema) (typ s
}
// additionalProperties with typed sub-schema
if schema.AdditionalProperties != nil && schema.AdditionalProperties.AdditionalPropertiesBool == nil {
ap := (*jsonschema.Schema)(schema.AdditionalProperties)
ap := (*Schema)(schema.AdditionalProperties)
apName := g.getSchemaName("", ap)
subTyp, err := g.processSchema(apName, ap)
if err != nil {
Expand Down Expand Up @@ -266,7 +265,7 @@ func contains(s []string, e string) bool {
return false
}

func getOrderedKeyNamesFromSchemaMap(m map[string]*jsonschema.Schema) []string {
func getOrderedKeyNamesFromSchemaMap(m map[string]*Schema) []string {
keys := make([]string, len(m))
idx := 0
for k := range m {
Expand Down Expand Up @@ -309,7 +308,7 @@ func getPrimitiveTypeName(schemaType string, subType string, pointer bool) (name
}

// return a name for this (sub-)schema.
func (g *Generator) getSchemaName(keyName string, schema *jsonschema.Schema) (string) {
func (g *Generator) getSchemaName(keyName string, schema *Schema) (string) {
if len(schema.Title) > 0 {
return getGolangName(schema.Title)
}
Expand Down
Loading

0 comments on commit 57add5d

Please sign in to comment.