Skip to content

Commit

Permalink
To schema prototype (#3)
Browse files Browse the repository at this point in the history
* chore: go.mod

* feat: ToSchema() implementation

* chore: _examples

* refactor: add tag

* feat: handling type value (options)

* feat: support specialized options

* feat: support field value (option)

* chore: update examples

* style: remove done task (comments)

* feat: array support

* fix: support patternProperties

* docs: remove done todo
  • Loading branch information
podhmo committed May 3, 2023
1 parent d9017e1 commit 61ff967
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 21 deletions.
39 changes: 39 additions & 0 deletions builder/_examples/main.go
@@ -0,0 +1,39 @@
package main

import (
"encoding/json"
"os"

"github.com/podhmo/gos/builder"
)

func main() {
b := builder.New()

Name := b.String().MinLength(1).As("Name")

b.Object(
b.Field("name", b.String()).Doc("name of person"),
b.Field("age", b.Integer().Format("int32")),
b.Field("nickname", b.Reference(Name)).Required(false),
b.Field("father", b.ReferenceByName("Person")).Required(false),
b.Field("friends", b.Array(b.ReferenceByName("Person"))).Required(false),
).As("Person").Doc("person object")

b.Object(
b.Field("title", b.String()),
b.Field("tests", b.Map(b.Integer()).
PatternProperties(`\-score$`, b.Integer().Doc("score (0~100)")).
PatternProperties(`\-grade$`, b.String().Doc("grade (A,B,C,D,E,F)"))),
).As("TestScore")

doc, err := builder.ToSchema(b)
if err != nil {
panic(err)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(doc); err != nil {
panic(err)
}
}
44 changes: 25 additions & 19 deletions builder/builder.go
Expand Up @@ -5,6 +5,8 @@ import (
"io"
"strings"
"sync"

"github.com/iancoleman/orderedmap"
)

func New() *Builder {
Expand All @@ -14,6 +16,7 @@ func New() *Builder {
type TypeBuilder interface {
typevalue() *Type
WriteType(io.Writer) error
ToSchema(b *Builder) *orderedmap.OrderedMap
}

type Builder struct {
Expand Down Expand Up @@ -206,19 +209,19 @@ func (t *type_[R]) As(name string) R {

type Type struct {
id int
Name string
Description string
Format string
Name string `json:"-"`
Description string `json:"description,omitempty"`
Format string `json:"format,omitempty"`

IsNewType bool
IsNewType bool `json:"-"`

underlying string
underlying string `json:"-"`
}

type Field struct {
Name string
Description string
Required bool
Name string `json:"-"`
Description string `json:"description,omitempty"`
Required bool `json:"-"`
}

type field[R any] struct {
Expand Down Expand Up @@ -264,9 +267,9 @@ func (t *StringBuilder[R]) Pattern(s string) R {
}

type String struct {
MinLength int64
MaxLength int64
Pattern string
MinLength int64 `json:"minlength,omitempty"`
MaxLength int64 `json:"maxlength,omitempty"`
Pattern string `json:"pattern,omitempty"`
}

type IntegerBuilder[R TypeBuilder] struct {
Expand All @@ -287,8 +290,8 @@ func (t *IntegerBuilder[R]) Maximum(n int64) R {

type Integer struct {
// minimum ≤ value ≤ maximum
Maximum int64
Minimum int64
Maximum int64 `json:"maximum,omitempty"`
Minimum int64 `json:"minimum,omitempty"`
}

// composite type
Expand All @@ -304,7 +307,7 @@ func (b *ObjectBuilder[R]) String(v bool) R {
}

type Object struct {
Strict bool
Strict bool `json:"-"`
}

type ArrayBuilder[T TypeBuilder, R TypeBuilder] struct {
Expand All @@ -323,8 +326,8 @@ func (t *ArrayBuilder[T, R]) MaxItems(n int64) R {
}

type Array struct {
MaxItems int64
MinItems int64
MaxItems int64 `json:"maxitems,omitempty"`
MinItems int64 `json:"minitems,omitempty"`
}

// string only map
Expand All @@ -334,11 +337,14 @@ type MapBuilder[V TypeBuilder, R TypeBuilder] struct {
value *Map
}

func (t *MapBuilder[T, R]) PatternProperties(s string) R {
t.value.PatternProperties = s
func (t *MapBuilder[T, R]) PatternProperties(s string, typ TypeBuilder) R {
if t.value.PatternProperties == nil {
t.value.PatternProperties = map[string]TypeBuilder{}
}
t.value.PatternProperties[s] = typ
return t.ret
}

type Map struct {
PatternProperties string
PatternProperties map[string]TypeBuilder `json:"-,omitempty"`
}
4 changes: 2 additions & 2 deletions builder/go.mod
Expand Up @@ -3,6 +3,6 @@ module github.com/podhmo/gos/builder
go 1.19

require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/google/go-cmp v0.5.9
github.com/iancoleman/orderedmap v0.2.0
)
143 changes: 143 additions & 0 deletions builder/to_schema.go
@@ -0,0 +1,143 @@
package builder

import (
"fmt"
"log"

"github.com/iancoleman/orderedmap"
"github.com/podhmo/gos/builder/maplib"
)

type toSchema interface {
ToSchema(*Builder) *orderedmap.OrderedMap
}

func ToSchema(b *Builder) (*orderedmap.OrderedMap, error) {
doc := orderedmap.New()

components := orderedmap.New()
doc.Set("components", components)

schemas := orderedmap.New()
components.Set("schemas", schemas)

if err := b.EachTypes(func(t TypeBuilder) error {
name := t.typevalue().Name
if t, ok := t.(toSchema); ok {
schemas.Set(name, t.ToSchema(b))
} else {
schemas.Set(name, orderedmap.New())
}
return nil
}); err != nil {
return doc, fmt.Errorf("each types -- %w", err)
}
return doc, nil
}

// customization
func (t *type_[R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := orderedmap.New()
doc.Set("type", t.value.underlying)
doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}

func (t *StringBuilder[R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := t.type_.ToSchema(b)
doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}
func (t *IntegerBuilder[R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := t.type_.ToSchema(b)
doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}
func (t *ArrayBuilder[T, R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := t.type_.ToSchema(b)
doc.Set("items", t.items.ToSchema(b))
doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}
func (t *MapBuilder[V, R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := t.type_.ToSchema(b)
doc.Set("type", "object")
if t.value.PatternProperties == nil {
doc.Set("additionalProperties", t.items.ToSchema(b))
} else {
props := orderedmap.New()
for k, typ := range t.value.PatternProperties {
props.Set(k, typ.ToSchema(b))
}
doc.Set("patternProperties", props)
}

doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}

func (t *ObjectBuilder[R]) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := t.type_.ToSchema(b)
required := make([]string, 0, len(t.Fields))
if len(t.Fields) > 0 {
properties := orderedmap.New()
for _, f := range t.Fields {
v := f.value
name := v.Name
if v.Required {
required = append(required, name)
}

var def *orderedmap.OrderedMap
if t, ok := f.typ.(toSchema); ok {
def = t.ToSchema(b)
} else {
def = orderedmap.New()
}
def, err := maplib.Merge(def, f.value)
if err != nil {
panic(err)
}
properties.Set(name, def)
}
doc.Set("properties", properties)
}

if len(required) > 0 {
doc.Set("required", required)
}
doc.Set("additionalProperties", false)
doc, err := maplib.Merge(doc, t.value)
if err != nil {
panic(err)
}
return doc
}

func (t TypeRef) ToSchema(b *Builder) *orderedmap.OrderedMap {
doc := orderedmap.New()
typ := t.getType()
if typ == nil {
log.Printf("#/components/schemas/%s is not found", t.Name)
doc.Set("$ref", fmt.Sprintf("#/components/schemas/%s", t.Name))
} else {
doc.Set("$ref", fmt.Sprintf("#/components/schemas/%s", typ.typevalue().Name))
}

return doc
}

0 comments on commit 61ff967

Please sign in to comment.