Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pacedotdev/oto
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.10.6
Choose a base ref
...
head repository: pacedotdev/oto
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.10.7
Choose a head ref
  • 20 commits
  • 16 files changed
  • 2 contributors

Commits on Sep 29, 2020

  1. removed "hi"

    matryer committed Sep 29, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3b17704 View commit details

Commits on Sep 30, 2020

  1. ignored that file again

    matryer committed Sep 30, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    24a8bc8 View commit details
  2. Copy the full SHA
    70b7083 View commit details
  3. Copy the full SHA
    9cebd7c View commit details
  4. removed THAT file

    matryer committed Sep 30, 2020
    Copy the full SHA
    a9b374d View commit details
  5. added status code check

    matryer committed Sep 30, 2020
    Copy the full SHA
    6bfe790 View commit details
  6. Copy the full SHA
    55a68aa View commit details
  7. Copy the full SHA
    1879f55 View commit details
  8. removed bad import

    matryer committed Sep 30, 2020
    Copy the full SHA
    d0a0b13 View commit details
  9. added service error support

    matryer committed Sep 30, 2020
    Copy the full SHA
    ebbd88f View commit details

Commits on Oct 1, 2020

  1. removed nonsense suggestion

    matryer committed Oct 1, 2020
    Copy the full SHA
    c99a14d View commit details
  2. style tweak

    matryer committed Oct 1, 2020
    Copy the full SHA
    ce115e5 View commit details

Commits on Oct 20, 2020

  1. added blog and video links

    matryer authored Oct 20, 2020
    Copy the full SHA
    b3d1535 View commit details
  2. added image

    matryer committed Oct 20, 2020
    Copy the full SHA
    33253a6 View commit details

Commits on Oct 29, 2020

  1. Copy the full SHA
    875c675 View commit details

Commits on Nov 1, 2020

  1. added test for strange types

    matryer committed Nov 1, 2020
    Copy the full SHA
    7fd213a View commit details
  2. Copy the full SHA
    1760a73 View commit details

Commits on Nov 13, 2020

  1. Copy the full SHA
    414ea93 View commit details
  2. Copy the full SHA
    fde060b View commit details

Commits on Nov 15, 2020

  1. Copy the full SHA
    ea09603 View commit details
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -15,5 +15,4 @@
oto
.DS_Store
dist
example/swift/SwiftCLIExample/SwiftCLIExample.xcodeproj/project.xcworkspace/xcuserdata/matryer.xcuserdatad/UserInterfaceState.xcuserstate
example/swift/SwiftCLIExample/SwiftCLIExample.xcodeproj/project.xcworkspace/xcuserdata/matryer.xcuserdatad/UserInterfaceState.xcuserstate
example/swift/SwiftCLIExample/SwiftCLIExample.xcodeproj/project.xcworkspace/xcuserdata/
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -14,6 +14,14 @@ These templates are already being used in production.
* There are some [official Oto templates](https://github.com/pacedotdev/oto/tree/master/otohttp/templates)
* The [Pace CLI tool](https://github.com/pacedotdev/pace/blob/master/oto/cli.go.plush) is generated from an open-source CLI template

## Learn

![](oto-video-preview.jpg)

* VIDEO: [Mat Ryer gives an overview of Oto at the Belfast Gophers meetup](https://www.youtube.com/watch?feature=youtu.be&v=DUg4ZITwMys)

* BLOG: [How code generation wrote our API and CLI](https://pace.dev/blog/2020/07/27/how-code-generation-wrote-our-api-and-cli.html)

## Tutorial

Install the project:
31 changes: 30 additions & 1 deletion example/client.swift.plush
Original file line number Diff line number Diff line change
@@ -17,7 +17,8 @@ class OtoClient {
}
<%= for (method) in service.Methods { %>
<%= format_comment_text(method.Comment) %> func <%= camelize_down(method.Name) %>(withRequest <%= camelize_down(method.InputObject.TypeName) %>: <%= method.InputObject.TypeName %>, completion: @escaping (_ response: <%= method.OutputObject.TypeName %>?, _ error: Error?) -> ()) {
var request = URLRequest(url: URL(string: "\(self.client.endpoint)/<%= service.Name %>.<%= method.Name %>")!)
let url = "\(self.client.endpoint)/<%= service.Name %>.<%= method.Name %>"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
@@ -35,13 +36,27 @@ class OtoClient {
completion(nil, err)
return
}
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode != 200) {
let err = OtoError("\(url): \(httpResponse.statusCode) status code")
completion(nil, err)
return
}
}
var <%= camelize_down(method.OutputObject.TypeName) %>: <%= method.OutputObject.TypeName %>
do {
<%= camelize_down(method.OutputObject.TypeName) %> = try JSONDecoder().decode(<%= method.OutputObject.TypeName %>.self, from: data!)
} catch let err {
completion(nil, err)
return
}
if let serviceErr = <%= camelize_down(method.OutputObject.TypeName) %>.error {
if (serviceErr != "") {
let err = OtoError(serviceErr)
completion(nil, err)
return
}
}
completion(<%= camelize_down(method.OutputObject.TypeName) %>, nil)
}
task.resume()
@@ -57,3 +72,17 @@ class OtoClient {
<% } %>
}
<% } %>

struct OtoError: LocalizedError
{
var errorDescription: String? { return message }
var failureReason: String? { return message }
var recoverySuggestion: String? { return "" }
var helpAnchor: String? { return "" }

private var message : String

init(_ description: String) {
message = description
}
}
11 changes: 11 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"io"
"log"
"net/http"

@@ -30,3 +31,13 @@ func main() {
fmt.Println("listening at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

// statusCodeHandler is useful for testing the server by returning a
// specific HTTP status code.
// http.Handle("/", statusCodeHandler(http.StatusInternalServerError))
type statusCodeHandler int

func (c statusCodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(int(c))
io.WriteString(w, http.StatusText(int(c)))
}
6 changes: 3 additions & 3 deletions example/server.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions example/server.go.plush
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ package <%= def.PackageName %>

import (
"context"
"log"
"net/http"

"github.com/pacedotdev/oto/otohttp"
@@ -16,7 +15,7 @@ import (
<%= for (service) in def.Services { %>
<%= format_comment_text(service.Comment) %>type <%= service.Name %> interface {
<%= for (method) in service.Methods { %>
<%= method.Name %>(context.Context, <%= method.InputObject.TypeName %>) (*<%= method.OutputObject.TypeName %>, error)<% } %>
<%= format_comment_text(method.Comment) %><%= method.Name %>(context.Context, <%= method.InputObject.TypeName %>) (*<%= method.OutputObject.TypeName %>, error)<% } %>
}
<% } %>

@@ -43,8 +42,7 @@ func (s *<%= camelize_down(service.Name) %>Server) handle<%= method.Name %>(w ht
}
response, err := s.<%= camelize_down(service.Name) %>.<%= method.Name %>(r.Context(), request)
if err != nil {
log.Println("TODO: oto service error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
s.server.OnErr(w, r, err)
return
}
if err := otohttp.Encode(w, r, http.StatusOK, response); err != nil {
@@ -57,7 +55,7 @@ func (s *<%= camelize_down(service.Name) %>Server) handle<%= method.Name %>(w ht

<%= for (object) in def.Objects { %>
<%= format_comment_text(object.Comment) %>type <%= object.Name %> struct {
<%= for (field) in object.Fields { %><%= format_comment_text(field.Comment) %><%= field.Name %> <%= if (field.Type.Multiple == true) { %>[]<% } %><%= field.Type.TypeName %> `json:"<%= camelize_down(field.Name) %><%= if (field.OmitEmpty) { %>,omitempty<% } %>"`
<%= for (field) in object.Fields { %><%= format_comment_text(field.Comment) %><%= field.Name %> <%= if (field.Type.Multiple == true) { %>[]<% } %><%= field.Type.TypeName %> `json:"<%= field.NameLowerCamel %><%= if (field.OmitEmpty) { %>,omitempty<% } %>"`
<% } %>
}
<% } %>
Binary file not shown.
31 changes: 30 additions & 1 deletion example/swift/SwiftCLIExample/SwiftCLIExample/client.gen.swift
Original file line number Diff line number Diff line change
@@ -19,7 +19,8 @@ class GreeterService {

// Greet prepares a lovely greeting.
func greet(withRequest greetRequest: GreetRequest, completion: @escaping (_ response: GreetResponse?, _ error: Error?) -> ()) {
var request = URLRequest(url: URL(string: "\(self.client.endpoint)/GreeterService.Greet")!)
let url = "\(self.client.endpoint)/GreeterService.Greet"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
@@ -37,13 +38,27 @@ class GreeterService {
completion(nil, err)
return
}
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode != 200) {
let err = OtoError("\(url): \(httpResponse.statusCode) status code")
completion(nil, err)
return
}
}
var greetResponse: GreetResponse
do {
greetResponse = try JSONDecoder().decode(GreetResponse.self, from: data!)
} catch let err {
completion(nil, err)
return
}
if let serviceErr = greetResponse.error {
if (serviceErr != "") {
let err = OtoError(serviceErr)
completion(nil, err)
return
}
}
completion(greetResponse, nil)
}
task.resume()
@@ -72,3 +87,17 @@ struct GreetResponse: Encodable, Decodable {

}


struct OtoError: LocalizedError
{
var errorDescription: String? { return message }
var failureReason: String? { return message }
var recoverySuggestion: String? { return "" }
var helpAnchor: String? { return "" }

private var message : String

init(_ description: String) {
message = description
}
}
1 change: 0 additions & 1 deletion example/swift/SwiftCLIExample/SwiftCLIExample/main.swift
Original file line number Diff line number Diff line change
@@ -20,5 +20,4 @@ greeterService.greet(withRequest: GreetRequest(
print(response!.greeting!)
}

print("hi")
sleep(1)
Binary file added oto-video-preview.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 24 additions & 2 deletions otohttp/templates/client.swift.plush
Original file line number Diff line number Diff line change
@@ -17,7 +17,8 @@ class OtoClient {
}
<%= for (method) in service.Methods { %>
<%= format_comment_text(method.Comment) %> func <%= camelize_down(method.Name) %>(withRequest <%= camelize_down(method.InputObject.TypeName) %>: <%= method.InputObject.TypeName %>, completion: @escaping (_ response: <%= method.OutputObject.TypeName %>?, _ error: Error?) -> ()) {
var request = URLRequest(url: URL(string: "\(self.client.endpoint)/<%= service.Name %>.<%= method.Name %>")!)
let url = "\(self.client.endpoint)/<%= service.Name %>.<%= method.Name %>"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
@@ -35,6 +36,13 @@ class OtoClient {
completion(nil, err)
return
}
if let httpResponse = response as? HTTPURLResponse {
if (httpResponse.statusCode != 200) {
let err = OtoError("\(url): \(httpResponse.statusCode) status code")
completion(nil, err)
return
}
}
var <%= camelize_down(method.OutputObject.TypeName) %>: <%= method.OutputObject.TypeName %>
do {
<%= camelize_down(method.OutputObject.TypeName) %> = try JSONDecoder().decode(<%= method.OutputObject.TypeName %>.self, from: data!)
@@ -53,7 +61,21 @@ class OtoClient {
<%= for (object) in def.Objects { %>
<%= format_comment_text(object.Comment) %>struct <%= object.Name %>: Encodable, Decodable {
<%= for (field) in object.Fields { %>
<%= format_comment_text(field.Comment) %> var <%= camelize_down(field.Name) %>: <%= field.Type.SwiftType %>?
<%= format_comment_text(field.Comment) %> var <%= camelize_down(field.Name) %>: <%= if (field.Type.IsObject) { %><%= field.Type.TypeName %><% } else { %> <%= field.Type.SwiftType %><% } %>?
<% } %>
}
<% } %>

struct OtoError: LocalizedError
{
var errorDescription: String? { return message }
var failureReason: String? { return message }
var recoverySuggestion: String? { return "" }
var helpAnchor: String? { return "" }

private var message : String

init(_ description: String) {
message = description
}
}
3 changes: 3 additions & 0 deletions otohttp/templates/client.ts.plush
Original file line number Diff line number Diff line change
@@ -35,6 +35,9 @@ export class Client {
headers: headers,
body: JSON.stringify(<%= camelize_down(method.InputObject.TypeName) %>),
})
if (response.status !== 200) {
throw new Error(`<%= service.Name %>.<%= method.Name %>: ${response.status} ${response.statusText}`);
}
return response.json().then((json) => {
if (json.error) {
throw new Error(json.error);
4 changes: 1 addition & 3 deletions otohttp/templates/server.go.plush
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ package <%= def.PackageName %>

import (
"context"
"log"
"net/http"

"github.com/pacedotdev/oto/otohttp"
@@ -43,8 +42,7 @@ func (s *<%= camelize_down(service.Name) %>Server) handle<%= method.Name %>(w ht
}
response, err := s.<%= camelize_down(service.Name) %>.<%= method.Name %>(r.Context(), request)
if err != nil {
log.Println("TODO: oto service error:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
s.server.OnErr(w, r, err)
return
}
if err := otohttp.Encode(w, r, http.StatusOK, response); err != nil {
30 changes: 30 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
@@ -45,6 +45,36 @@ func (d *Definition) Object(name string) (*Object, error) {
return nil, ErrNotFound
}

// ObjectIsInput gets whether this object is a method
// input (request) type or not.\
// Returns true if any method.InputObject.ObjectName matches
// name.
func (d *Definition) ObjectIsInput(name string) bool {
for _, service := range d.Services {
for _, method := range service.Methods {
if method.InputObject.ObjectName == name {
return true
}
}
}
return false
}

// ObjectIsOutput gets whether this object is a method
// output (response) type or not.
// Returns true if any method.OutputObject.ObjectName matches
// name.
func (d *Definition) ObjectIsOutput(name string) bool {
for _, service := range d.Services {
for _, method := range service.Methods {
if method.OutputObject.ObjectName == name {
return true
}
}
}
return false
}

// Service describes a service, akin to an interface in Go.
type Service struct {
Name string `json:"name"`
44 changes: 32 additions & 12 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ func TestParse(t *testing.T) {
is.NoErr(err)

is.Equal(def.PackageName, "pleasantries")
is.Equal(len(def.Services), 2) // should be 2 services
is.Equal(len(def.Services), 3) // should be 3 services
is.Equal(def.Services[0].Name, "GreeterService")
is.Equal(def.Services[0].Metadata["strapline"], "A lovely greeter service") // custom metadata
is.Equal(def.Services[0].Comment, `GreeterService is a polite API.
@@ -102,17 +102,22 @@ You will love it.`)
is.Equal(greetOutputObject.Fields[1].Type.Multiple, false)
is.Equal(greetOutputObject.Fields[1].Type.Package, "")

is.Equal(def.Services[1].Name, "Welcomer")
is.Equal(len(def.Services[1].Methods), 1)
is.Equal(def.Services[1].Name, "StrangeTypesService")
strangeInputObj, err := def.Object(def.Services[1].Methods[0].InputObject.ObjectName)
is.NoErr(err)
is.Equal(strangeInputObj.Fields[0].Type.JSType, "any")

is.Equal(def.Services[2].Name, "Welcomer")
is.Equal(len(def.Services[2].Methods), 1)

is.Equal(def.Services[1].Methods[0].InputObject.TypeName, "WelcomeRequest")
is.Equal(def.Services[1].Methods[0].InputObject.Multiple, false)
is.Equal(def.Services[1].Methods[0].InputObject.Package, "")
is.Equal(def.Services[1].Methods[0].OutputObject.TypeName, "WelcomeResponse")
is.Equal(def.Services[1].Methods[0].OutputObject.Multiple, false)
is.Equal(def.Services[1].Methods[0].OutputObject.Package, "")
is.Equal(def.Services[2].Methods[0].InputObject.TypeName, "WelcomeRequest")
is.Equal(def.Services[2].Methods[0].InputObject.Multiple, false)
is.Equal(def.Services[2].Methods[0].InputObject.Package, "")
is.Equal(def.Services[2].Methods[0].OutputObject.TypeName, "WelcomeResponse")
is.Equal(def.Services[2].Methods[0].OutputObject.Multiple, false)
is.Equal(def.Services[2].Methods[0].OutputObject.Package, "")

welcomeInputObject, err := def.Object(def.Services[1].Methods[0].InputObject.TypeName)
welcomeInputObject, err := def.Object(def.Services[2].Methods[0].InputObject.TypeName)
is.NoErr(err)
is.Equal(welcomeInputObject.Name, "WelcomeRequest")
is.Equal(len(welcomeInputObject.Fields), 4)
@@ -146,7 +151,7 @@ You will love it.`)
is.Equal(welcomeInputObject.Fields[3].Type.JSType, "boolean")
is.Equal(welcomeInputObject.Fields[3].Type.SwiftType, "Bool")

welcomeOutputObject, err := def.Object(def.Services[1].Methods[0].OutputObject.TypeName)
welcomeOutputObject, err := def.Object(def.Services[2].Methods[0].OutputObject.TypeName)
is.NoErr(err)
is.Equal(welcomeOutputObject.Name, "WelcomeResponse")
is.Equal(len(welcomeOutputObject.Fields), 2)
@@ -167,7 +172,7 @@ You will love it.`)
is.Equal(welcomeOutputObject.Fields[1].Type.SwiftType, "String")
is.True(welcomeOutputObject.Metadata != nil)

is.Equal(len(def.Objects), 8)
is.Equal(len(def.Objects), 10)
for i := range def.Objects {
switch def.Objects[i].Name {
case "Greeting":
@@ -203,3 +208,18 @@ func TestExtractCommentMetadata(t *testing.T) {
is.Equal(metadata["required"], true)
is.Equal(metadata["monkey"], float64(24))
}

func TestObjectIsInputOutput(t *testing.T) {
is := is.New(t)
patterns := []string{"./testdata/services/pleasantries"}
parser := New(patterns...)
parser.Verbose = testing.Verbose()
parser.ExcludeInterfaces = []string{"Ignorer"}
def, err := parser.Parse()
is.NoErr(err)

is.Equal(def.ObjectIsInput("GreetRequest"), true)
is.Equal(def.ObjectIsInput("GreetResponse"), false)
is.Equal(def.ObjectIsOutput("GreetRequest"), false)
is.Equal(def.ObjectIsOutput("GreetResponse"), true)
}
14 changes: 14 additions & 0 deletions parser/testdata/services/pleasantries/strange_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pleasantries

type StrangeTypesService interface {
DoSomethingStrange(DoSomethingStrangeRequest) DoSomethingStrangeResponse
}

type DoSomethingStrangeRequest struct {
Anything interface{}
}

type DoSomethingStrangeResponse struct {
Value interface{}
Size int
}