Added a repository for API source of truth
This commit adds a new package repository containing every single cchat types that the package provides. Its goal is to be the source of truth for the cchat files to be generated from. A huge advantage of this is having types in an easily representable format. This means that other languages can easily parse the repository and generate its own types that are similar to the original ones. Having a repository also allows easier code generation. For example, this commit will allow generating the "empty" package in the future, which would contain empty implementations of cchat databases that would return nil for asserter methods.
This commit is contained in:
parent
8b8c46a714
commit
8e9321928b
6
go.mod
6
go.mod
|
@ -2,4 +2,8 @@ module github.com/diamondburned/cchat
|
|||
|
||||
go 1.14
|
||||
|
||||
require github.com/pkg/errors v0.9.1
|
||||
require (
|
||||
github.com/dave/jennifer v1.4.1
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/pkg/errors v0.9.1
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,2 +1,6 @@
|
|||
github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
|
||||
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
|
||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/doc"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// commentTrimSurrounding is a regex to trim surrounding new line and tabs.
|
||||
// This is needed to find the correct level of indentation.
|
||||
commentTrimSurrounding = regexp.MustCompile(`(^\n)|(\n\t+$)`)
|
||||
)
|
||||
|
||||
// Comment represents a raw comment string. Most use cases should use GoString()
|
||||
// to get the comment's content.
|
||||
type Comment struct {
|
||||
RawText string
|
||||
}
|
||||
|
||||
// GoString formats the documentation string in 80 columns wide paragraphs.
|
||||
func (c Comment) GoString() string {
|
||||
return c.WrapText(80)
|
||||
}
|
||||
|
||||
// WrapText wraps the raw text in n columns wide paragraphs.
|
||||
func (c Comment) WrapText(column int) string {
|
||||
var buf bytes.Buffer
|
||||
doc.ToText(&buf, c.Unindent(), "", "\t", column)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Unindent removes the indentations that were there for the sake of syntax in
|
||||
// RawText. It gets the lowest indentation level from each line and trim it.
|
||||
func (c Comment) Unindent() string {
|
||||
// Trim new lines.
|
||||
txt := commentTrimSurrounding.ReplaceAllString(c.RawText, "")
|
||||
|
||||
// Split the lines and rejoin them later to trim the indentation.
|
||||
var lines = strings.Split(txt, "\n")
|
||||
var indent = 0
|
||||
|
||||
// Get the minimum indentation count.
|
||||
for _, line := range lines {
|
||||
linedent := strings.Count(line, "\t")
|
||||
if linedent < 0 {
|
||||
continue
|
||||
}
|
||||
if linedent < indent || indent == 0 {
|
||||
indent = linedent
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the indentation.
|
||||
if indent > 0 {
|
||||
for i, line := range lines {
|
||||
if len(line) > 0 {
|
||||
lines[i] = line[indent-1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rejoin.
|
||||
txt = strings.Join(lines, "\n")
|
||||
|
||||
return txt
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
const _comment = `
|
||||
The authenticator interface allows for a multistage initial authentication API
|
||||
that the backend could use. Multistage is done by calling AuthenticateForm then
|
||||
Authenticate again forever until no errors are returned.
|
||||
|
||||
var s *cchat.Session
|
||||
var err error
|
||||
|
||||
for {
|
||||
// Pseudo-function to render the form and return the results of those
|
||||
// forms when the user confirms it.
|
||||
outputs := renderAuthForm(svc.AuthenticateForm())
|
||||
|
||||
s, err = svc.Authenticate(outputs)
|
||||
if err != nil {
|
||||
renderError(errors.Wrap(err, "Error while authenticating"))
|
||||
continue // retry
|
||||
}
|
||||
|
||||
break // success
|
||||
}`
|
||||
|
||||
// Trim away the prefix new line.
|
||||
var comment = _comment[1:]
|
||||
|
||||
func TestComment(t *testing.T) {
|
||||
var authenticator = Main["cchat"].Interface("Authenticator")
|
||||
var authDoc = authenticator.Comment.GoString()
|
||||
|
||||
if eq := deep.Equal(comment, authDoc); eq != nil {
|
||||
t.Fatal("Comment inequality:", eq)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package repository
|
||||
|
||||
type Enumeration struct {
|
||||
Comment
|
||||
Name string
|
||||
Values []EnumValue
|
||||
Bitwise bool
|
||||
}
|
||||
|
||||
type EnumValue struct {
|
||||
Comment
|
||||
Name string // also return value from String()
|
||||
}
|
||||
|
||||
// IsPlaceholder returns true if the enumeration value is meant to be a
|
||||
// placeholder. In Go, it would look like this:
|
||||
//
|
||||
// const (
|
||||
// _ EnumType = iota // IsPlaceholder() == true
|
||||
// V1
|
||||
// )
|
||||
//
|
||||
func (v EnumValue) IsPlaceholder() bool {
|
||||
return v.Name == ""
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package repository
|
||||
|
||||
type ErrorType struct {
|
||||
Struct
|
||||
ErrorString TmplString // used for Error()
|
||||
}
|
||||
|
||||
// Wrapped returns true if the error struct contains a field with the error
|
||||
// type.
|
||||
func (t ErrorType) Wrapped() bool {
|
||||
for _, ret := range t.Struct.Fields {
|
||||
if ret.Type == "error" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package xml
|
||||
|
||||
//go:generate go run ./generator.go
|
|
@ -0,0 +1,24 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/diamondburned/cchat/repository"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Create("repository.gob")
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to create file:", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := gob.NewEncoder(f).Encode(repository.Main); err != nil {
|
||||
os.Remove("repository.gob")
|
||||
log.Fatal("Failed to gob encode:", err)
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
package repository
|
||||
|
||||
import "strings"
|
||||
|
||||
type Interface struct {
|
||||
Comment
|
||||
Name string
|
||||
Embeds []EmbeddedInterface
|
||||
Methods []Method // actual methods
|
||||
}
|
||||
|
||||
type EmbeddedInterface struct {
|
||||
Comment
|
||||
InterfaceName string
|
||||
}
|
||||
|
||||
// IsContainer returns true if the interface is a frontend container interface,
|
||||
// that is when its name has "Container" at the end.
|
||||
func (i Interface) IsContainer() bool {
|
||||
return strings.HasSuffix(i.Name, "Container")
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
func TestGob(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := gob.NewEncoder(&buf).Encode(Main); err != nil {
|
||||
t.Fatal("Failed to gob encode:", err)
|
||||
}
|
||||
|
||||
t.Log("Marshaled; total bytes:", buf.Len())
|
||||
|
||||
var unmarshaled Repositories
|
||||
|
||||
if err := gob.NewDecoder(&buf).Decode(&unmarshaled); err != nil {
|
||||
t.Fatal("Failed to gob decode:", err)
|
||||
}
|
||||
|
||||
if eq := deep.Equal(Main, unmarshaled); eq != nil {
|
||||
t.Fatal("Inequalities after unmarshaling:", eq)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package repository
|
||||
|
||||
import "encoding/gob"
|
||||
|
||||
func init() {
|
||||
gob.Register(ContainerMethod{})
|
||||
gob.Register(AsserterMethod{})
|
||||
gob.Register(GetterMethod{})
|
||||
gob.Register(SetterMethod{})
|
||||
gob.Register(IOMethod{})
|
||||
}
|
||||
|
||||
type Method interface {
|
||||
UnderlyingName() string
|
||||
internalMethod()
|
||||
}
|
||||
|
||||
type RegularMethod struct {
|
||||
Comment
|
||||
Name string
|
||||
}
|
||||
|
||||
func (m RegularMethod) UnderlyingName() string { return m.Name }
|
||||
func (m RegularMethod) internalMethod() {}
|
||||
|
||||
// GetterMethod is a method that returns a regular value. These methods must not
|
||||
// do any IO. An example of one would be ID() returning ID.
|
||||
type GetterMethod struct {
|
||||
RegularMethod
|
||||
|
||||
// Parameters is the list of parameters in the function.
|
||||
Parameters []NamedType
|
||||
// Returns is the list of named types returned from the function.
|
||||
Returns []NamedType
|
||||
// ReturnError is true if the function returns an error at the end of
|
||||
// returns.
|
||||
ReturnError bool
|
||||
}
|
||||
|
||||
// SetterMethod is a method that sets values. These methods must not do IO, and
|
||||
// they have to be non-blocking.
|
||||
type SetterMethod struct {
|
||||
RegularMethod
|
||||
|
||||
// Parameters is the list of parameters in the function. These parameters
|
||||
// should be the parameters to set.
|
||||
Parameters []NamedType
|
||||
}
|
||||
|
||||
// IOMethod is a regular method that can do IO and thus is blocking. These
|
||||
// methods usually always return errors.
|
||||
type IOMethod struct {
|
||||
RegularMethod
|
||||
|
||||
// Parameters is the list of parameters in the function.
|
||||
Parameters []NamedType
|
||||
// ReturnValue is the return value in the function.
|
||||
ReturnValue NamedType
|
||||
// ReturnError is true if the function returns an error at the end of
|
||||
// returns.
|
||||
ReturnError bool
|
||||
}
|
||||
|
||||
// ContainerMethod is a method that uses a Container. These methods can do IO.
|
||||
type ContainerMethod struct {
|
||||
RegularMethod
|
||||
|
||||
// HasContext is true if the method accepts a context as its first argument.
|
||||
HasContext bool
|
||||
// ContainerType is the name of the container interface. The name will
|
||||
// almost always have "Container" as its suffix.
|
||||
ContainerType string
|
||||
// HasStopFn is true if the function returns a callback of type func() as
|
||||
// its first return. The function will return an error in addition. If this
|
||||
// is false, then only the error is returned.
|
||||
HasStopFn bool
|
||||
}
|
||||
|
||||
// AsserterMethod is a method that allows the parent interface to extend itself
|
||||
// into children interfaces. These methods must not do IO.
|
||||
type AsserterMethod struct {
|
||||
// ChildType is the children type that is returned.
|
||||
ChildType string
|
||||
}
|
||||
|
||||
func (m AsserterMethod) internalMethod() {}
|
||||
|
||||
// UnderlyingName returns the name of the method.
|
||||
func (m AsserterMethod) UnderlyingName() string {
|
||||
return "As" + m.ChildType
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package repository
|
||||
|
||||
// MainNamespace is the name of the namespace that should be top level.
|
||||
const MainNamespace = "cchat"
|
||||
|
||||
type Repositories map[string]Repository
|
||||
|
||||
type Repository struct {
|
||||
Enums []Enumeration
|
||||
TypeAliases []TypeAlias
|
||||
Structs []Struct
|
||||
ErrorTypes []ErrorType
|
||||
Interfaces []Interface
|
||||
}
|
||||
|
||||
// Interface finds an interface. Nil is returned if none is found.
|
||||
func (r Repository) Interface(name string) *Interface {
|
||||
for _, iface := range r.Interfaces {
|
||||
if iface.Name == name {
|
||||
return &iface
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NamedType struct {
|
||||
Name string // optional
|
||||
Type string
|
||||
}
|
||||
|
||||
// IsZero is true if t.Type is empty.
|
||||
func (t NamedType) IsZero() bool {
|
||||
return t.Type == ""
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package repository
|
||||
|
||||
type Struct struct {
|
||||
Comment
|
||||
Name string
|
||||
Fields []StructField
|
||||
}
|
||||
|
||||
type StructField struct {
|
||||
Comment
|
||||
NamedType
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package repository
|
||||
|
||||
// TmplString is a generation-time templated string. It is used for string
|
||||
// concatenation.
|
||||
//
|
||||
// Given the following TmplString:
|
||||
//
|
||||
// TmplString{Receiver: "v", Template: "Hello, {v.Foo()}"}
|
||||
//
|
||||
// The output of String() should be the same as the output of
|
||||
//
|
||||
// "Hello, " + v.Foo()
|
||||
//
|
||||
type TmplString struct {
|
||||
Receiver string
|
||||
Template string
|
||||
}
|
||||
|
||||
func (s TmplString) String() string {
|
||||
panic("TODO")
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package repository
|
||||
|
||||
// TypeAlias represents a Go type alias.
|
||||
type TypeAlias struct {
|
||||
Comment
|
||||
Name string
|
||||
Type string
|
||||
}
|
Loading…
Reference in New Issue