pacedotdev / oto Goto Github PK
View Code? Open in Web Editor NEWGo driven rpc code generation tool for right now.
License: MIT License
Go driven rpc code generation tool for right now.
License: MIT License
Given the following input:
type MyResponse struct {
IsOpen bool `json:"is_open,omitempty"`
// ClosedReasonCode defines the reason, why a shop can be closed. It is empty, when IsOpen is
// true. Possible values are: "holiday", "same_day_deadline", "out_of_season".
// The ClosedReasonMessage provides a user message that can be shown.
ClosedReasonCode string `json:"closed_reason_code,omitempty"`
}
the following output is generated:
type MyResponse struct {
IsOpen bool `json:"is_open,omitempty"`
// ClosedReasonCode defines the reason, why a shop can be closed. It is empty, when
// IsOpen is The ClosedReasonMessage provides a user message that can be shown.
ClosedReasonCode string `json:"closed_reason_code,omitempty"`
// Error is string explaining what went wrong. Empty if everything was fine.
Error string `json:"error,omitempty"`
}
I think this is related to the parsing of the : "
in the second comment line.
The comment should be copied as is.
Good morning!
Yesterday evening I was giving oto a try and found that time.Time
is not supported which made me think it was because I was on Windows at that time.
I just tried on Ubuntu with the same result:
parse input object type: parse type: parse type: /usr/local/go/src/time/time.go:139:2: wall must be exported
Which led me to think oto is not checking if a type satifies json.Marshaler and/or json.Unmarshaler.
Is this something that I should solve in my template?
Here's my sample definition:
package definition
import "time"
type ThingService interface {
CreateThing(CreateThingRequest) CreateThingResponse
}
type CreateThingRequest struct {
Thing Thing
}
type CreateThingResponse struct {
Result bool
}
type Thing struct {
ID int64
OwnerID int64
Type string
Props map[string]interface{}
Created time.Time
Enabled bool
}
I know I could fix this by using a string
as type for Created
instead but this feels weird as time.Time
is supported by encoding/json
.
Hi guys.
I was just testing oto and so far so good. I love it. It's simple and does with it suppose to do.
There is a small glitch in understanding on handle mapping that might put the brains of the week of heart in an infinite loop :)
Given the following code
srv := otohttp.NewServer()
RegisterHelloWorld(srv, helloWorldService)
http.Handle("/oto/", srv)
Works, but if I want to do this
srv := otohttp.NewServer()
RegisterHelloWorld(srv, helloWorldService)
http.Handle("/api/v2/", srv)
I get a 404. Took me a solid 30-45 mins until I decided to take a peek inside the lib to see the oto does a base map that defaults to /oto/
The only workaround I found but is ugly and redundant is to match patterns by overwriting the Basepath
srv := otohttp.NewServer()
srv.Basepath = "/api/v1/"
RegisterHelloWorld(srv, helloWorldService)
http.Handle("/api/v1/", srv)
Is there another way to do this?
In the following article, it mentioned generics. Now the go 1.18 is released and has generics support, I was wondering if there is plan to take advantage of generics?
How code generation wrote our API and CLI.
When generics lands in Go, we will be able to reduce the amount of boilerplate code generated in favour of generic methods. In this world, we may not need to generate much plumbing code at all.
// This is a comment.
// example: "Hi there"
// required: true
// monkey: "Hell yes"
With the following API definition and the client.ts.plush
template from the otohttp project, we get wrong TypeScript output. This can be reproduced with the current version, v0.11.0, but was not occurring with an older version, in my case v.0.10.14.
package definitions
type SessionService interface {
UpdateSession(UpdateSessionRequest) UpdateSessionResponse
}
type UpdateSessionRequest struct { Customer *Customer }
type UpdateSessionResponse struct {}
type Customer struct {}
The wrong TypeScript code:
// removed for brevity
export class UpdateSessionRequest {
constructor(data?: any) {
if (data) {
this.customer = new *Customer(data.customer);
}
}
customer?: *Customer;
}
// removed for brevity
Noticing the star before the Customer
? That's causing syntax errors.
A convenience feature could be to use http urls to reference templates:
mkdir generated
oto -template https://raw.githubusercontent.com/pacedotdev/oto/master/otohttp/templates/server.go.plush \
-out ./generated/oto.gen.go \
-ignore Ignorer \
-pkg generated \
./definitions
gofmt -w ./generated/oto.gen.go ./generated/oto.gen.go
oto -template https://raw.githubusercontent.com/pacedotdev/oto/master/otohttp/templates/client.js.plush \
-out ./generated/oto.gen.js \
-ignore Ignorer \
./definitions
```
Oto's autogenerated documentation doesn't properly encode special characters such as double quotes. I found this problem while reading pace's docs and reproduced it by myself. You can find an example here:
pkg.go.dev
: https://pkg.go.dev/github.com/pacedotdev/pace?tab=doc#AddCommentRequestPlease, feel free to close this issue if this is the intended behavior. I was just trying the tool and noticed it.
Best,
Miguel
The parser doesn't handle byte
and []byte
types when parsing the JSType. This will cause the generated code to have a type of []
if it is a []byte
. According to the JSON encoding documentation, []byte
types are base64 encoded to a string. The proper JSType should be a string
.
This can be handled in the template by checking if the "go type" is of byte
and that it is a multiple. This snippet can handle the case:
<%= for (object) in def.Objects { %>
<%= format_comment_text(object.Comment) %>export interface <%= object.Name %> {
<%= for (field) in object.Fields { %>
<%= format_comment_text(field.Comment) %> <%= field.NameLowerCamel %>: <%= if (field.Type.IsObject) { %><%= field.Type.TypeName %><% } else if (field.Type.TypeName == "byte" && field.Type.Multiple) { %>string<% } else { %><%= field.Type.JSType %><% } %><%= if (field.Type.Multiple && field.Type.TypeName != "byte") { %>[]<% } %>;
<% } %>
}
<% } %>
The cleaner way to handle this would be to do it in the parser but I couldn't find a nice way to do it with a major redesign. It would need to the determination between single and multi-type as it's parsing the JSType.
flags:
-ignore string
comma separated list of interfaces to ignore
Is it possible to add similar:
flags:
-include string
comma separated list of interfaces to generate
-list
comma separated list of all defined interfaces
I would like to have each interface generated in a separate file, so I would call something like:
for interface in `oto -list`; do
oto -out ./generated/${interface}/${interface}.go -pkg ${interface} -include ${interface}
done
Or is there any other simple method to generate separate files?
We have added a basic TypeScript client that uses the fetch
API to make Oto calls, like the JS client does.
Client
and pass that to the constructor for each service)Check it out: https://github.com/pacedotdev/oto/blob/master/otohttp/templates/client.ts.plush
In case there are many source templates, it would be helpful if the generated code pointed to the source template, like:
// Code generated from <%= def.SourceTemplate %>; DO NOT EDIT.
How would one drop down to the http.Request
or http.Response
structs from inside service implementations (as shown below) - for purposes such as accessing r.Header
/ r.AddCookie()
/ w.WriteHeader(400)
etc?
Lines 17 to 22 in df5a36e
It is possible to define:
type GreeterService interface {
GetGreetings(GetGreetingsRequest) GetGreetingsResponse
}
but some services return list of objects instead of object with a list of object, so:
type GreeterService interface {
GetGreetings(GetGreetingsRequest) []Greeting
}
but as I understand it is not currently supported. Could this be added?
When following the tutorial to install the project:
go install github.com/pacedotdev/oto
# github.com/pacedotdev/oto/parser
pacedotdev/oto/parser/parser.go:161:17: undefined: doc.NewFromFiles
I'm trying oto and thanks it's made creating a client lib so easy. I wonder could oto create an error object for the client-side ? I imagine something like this
// definition.go
type ServiceError int
const (
ErrInternal ServiceError = 1000
ErrPermissionDenined ServiceError = 1001
ErrInvalidArgument ServiceError = 1002
)
func (s ServiceError) Error() string {
switch s {
default: // ErrInternal
return "internal"
case ErrInvalidArgument:
return "invalid_argument"
case ErrPermissionDenined:
return "permission_denied"
}
}
// client.js
const ErrInvalidArgument = new Error('permission_denied')
// index.js
try {
res = await greeter.Greets(req)
} catch (err) {
switch (err.message) {
case greeter.ErrInvalidArgument:
// do something
case greeter.ErrPermissionDenined:
// do something
case greeter.ErrInternal:
// do something
}
}
First, thanks for this tool! I really like it so far.
I'm wondering a bit though how errors are supposed to be handled? Maybe I'm missing something, but what I'm seeing is this:
If I return a non-nil error from one of the service endpoints, a 500 is returned to the client with the body being the error message (https://github.com/pacedotdev/oto/blob/master/otohttp/templates/server.go.plush#L46-L47). On the client side, the response is assumed to be JSON though (https://github.com/pacedotdev/oto/blob/master/otohttp/templates/client.ts.plush#L38), which leads to a JS error like SyntaxError: Unexpected token c in JSON at position 0
. So out-of-the-box that seems a bit broken ... and I'm a bit confused why the "service endpoint error" is not simply handled with s.server.OnErr(w, r, err)
, like in the other error cases? Would be great if you could shed some light on this!
The oto API base path can be adjusted in principle via the JS template and through the Go-side server setup. However, /oto/
is also hardcoded at one location in otohttp
:
Line 45 in 650a128
If that was made configurable, users would be able to use a different base path (which is currently impossible without forking the project).
I was really interested in using the tips and tricks from:
https://grafana.com/blog/2024/02/09/how-i-write-http-services-in-go-after-13-years/
And since oto was advertised there, I was hoping to see these implemented.
I've been playing around with oto and one of the first things I wondered was why would the templates not be written in the text/template or the html/template?
And with that in mind I was thinking if a feature like a configurable templating engine would be interesting from your point of view.
The benefit is that new comers to oto, who don't know plush (like me), don't have to go and learn a new templating language. There's another, more subjective thing that could be considered: "if the standard lib has what I need, why go to a third party alternative".
The interface for the command line could be a new optional parameter --engine {text/template|html/template[|plush]}
. The absence would mean the plush engine and that would preserve compatibility.
I've glanced at plush but at first sight couldn't see the need for it over the std lib (could be an historical thing, like at some point in the past the Go templating engines weren't enough), so if its something obvious I'd love to understand, should you know it.
I want to support polymophic discrimination through serialization. For example, see this short example:
package main
import (
"encoding/json"
"fmt"
)
type FruitType string
const (
AppleType FruitType = "Apple"
BananaType FruitType = "Banana"
)
type Fruit interface {
Type() FruitType
}
type fruitWrapper struct {
Type FruitType `json:"type"`
}
type Apple struct {
Type FruitType `json:"type"`
Variety string `json:"variety"`
}
func (Apple) Type() FruitType { return AppleType }
type Banana struct {
Type FruitType `json:"type"`
Length int `json:"length"`
}
func (Banana) Type() FruitType { return BananaType }
func (f *Fruit) UnmarshalJSON(data []byte) error {
var wrapper fruitWrapper
if err := json.Unmarshal(data, &wrapper); err != nil {
return err
}
switch wrapper.Type {
case AppleType:
var apple Apple
if err := json.Unmarshal(data, &apple); err != nil {
return err
}
*f = apple
case BananaType:
var banana Banana
if err := json.Unmarshal(data, &banana); err != nil {
return err
}
*f = banana
default:
return fmt.Errorf("unknown type")
}
return nil
}
func main() {
jsonData := []byte(`{"type":"Apple","variety":"Fuji"}`)
var fruit Fruit
if err := json.Unmarshal(jsonData, &fruit); err != nil {
fmt.Println(err)
return
}
switch f := fruit.(type) {
case Apple:
fmt.Println("Apple variety:", f.Variety)
case Banana:
fmt.Println("Banana length:", f.Length)
default:
fmt.Println("Unknown fruit")
}
}
To support this in oto, I suppose this function needs to support conditional unmarshalling, as above. To get correct TS code generation, we'd need to have all of the defined interfaces/structs tied together somehow, too.
Perhaps in the def, something like this:
type User interface {
// specializations: SpecificUser, AltUser, SomeOtherUserType
Type() UserType
}
...
// specializationOf: User
type SpecificUser struct {
then some mechanism to emit the correct code. I wonder at what the right level of codegen vs explicit specification is right, especially as Oto has the need to build these up in templates, too.
Is defining a validation schema beyond the scope of oto or is it something that's planned? (thinking something along the lines of swagger)
When defining additional struct tags, like the ones from the go-playground/validator package, they aren't copied to the generated oto.gen.go
file. I could define them again or do some other type of validation in the implementation, but I think this is a nice and clean way for things like that.
We are working on a Python client for Oto.
Can you help?
So, oto generates an error property with each response. Should I set this manually, or doing the go way? So, what of these approaches is better:
// this:
if err != nil {
return &MyServiceResponse{Error: "not found"}, nil
}
// or this?
if err != nil {
return nil, errors.New("not found")
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.