go-swagger-tutorial/docs/Part_02.md

223 lines
7.9 KiB
Markdown
Raw Normal View History

2018-03-31 02:05:11 +00:00
# Review of Part One
In
[Part One of Go-Swagger](http://www.elfsternberg.com/2018/03/30/writing-microservice-swagger-part-1-specification/),
we generated a on OpenAPI 2.0 server with REST endpoints. The server
builds and responds to queries, but every valid query ends with "This
feature has not yet been implemented."
2018-03-31 02:05:11 +00:00
It's time to implement the feature.
I want to emphasize that with Go Swagger there is *only* one generated
file you need to touch. Since our project is named `timeofday`, the
file will be named `restapi/configure_timeofday.go`. Our first step
2018-03-31 02:05:11 +00:00
will be to break those "not implemented" functions out into their own
Go package. That package will be our business logic. The configure
file and the business logic package will be the *only* things we
change.
A reminder: The final source code for this project is available on
Github, however Parts One & Two deal with the most common
implementation, a server with hard-coded default values. For these
chapters, please consult
[that specific version of the code](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0).
2018-03-31 02:05:11 +00:00
## Break out the business logic
Create a new folder in your project root and call it `timeofday`.
Open up your editor and find the file `restapi/configure_timeofday.go`.
In your `swagger.yml` file you created two endpoints and gave them each
an `operationId`: `TimePost` and `TimeGet`. Inside
2018-03-31 02:05:11 +00:00
`configure_timeofday.go`, you should find two corresponding assignments
in the function `configureAPI()`: `TimeGetHandlerFunc` and
`TimePostHandlerFunc`. Inside those function calls, you'll find
2018-03-31 02:05:11 +00:00
anonymous functions.
I want you to take those anonymous functions, cut them out, and paste
them into a new file inside the `timeofday/` folder. You will also have
to create a package name and import any packages being used. Now your
file, which I've called `timeofday/handlers.go`, looks like this (note
that you'll have to change your import paths as you're probably not
elfsternberg. Heck, _I'm_ probably not elfsternberg):
2018-03-31 02:05:11 +00:00
```
2018-03-31 02:05:11 +00:00
<<handlers.go before implementation>>=
package timeofday
import(
"github.com/go-openapi/runtime/middleware"
"github.com/elfsternberg/timeofday/restapi/operations"
)
func GetTime(params operations.TimeGetParams) middleware.Responder {
return middleware.NotImplemented("operation .TimeGet has not yet been implemented")
}
func PostTime(params operations.TimePostParams) middleware.Responder {
return middleware.NotImplemented("operation .TimePost has not yet been implemented")
}
@
```
2018-03-31 02:05:11 +00:00
And now go back to `restapi/configure_timeofday.go`, add
`github.com/elfsternberg/timeofday/clock` to the imports, and change the
handler lines to look like this:
```
2018-03-31 02:05:11 +00:00
<<configuration lines before implementation>>=
api.TimeGetHandler = operations.TimeGetHandlerFunc(timeofday.GetTime)
api.TimePostHandler = operations.TimePostHandlerFunc(timeofday.PostTime)
@
```
2018-03-31 02:05:11 +00:00
## Implementation
Believe it or not, you've now done everything you need to do except the
business logic. We're going to honor the point of OpenAPI and the `//
DO NOT EDIT`` comments, and not modify anything exceept the contents of
our handler.
To understand our code, though, we're going to have to *read* some of
those files. Let's go look at `/models`. In here, you'll find the
schemas you outlined in the `swagger.yml` file turned into source code.
If you open one, like many files generated by Swagger, you'll see it
reads `// DO NOT EDIT`. But then there's that function there,
`Validate()`. What if you want to do advanced validation for custom
patterns or inter-field relations not covered by Swagger's validators?
Well, you'll have to edit this file. And figure out how to live with
it. We're not going to do that here. This exercise is about *not*
editing those files. But we can see, for example, that the `Timezone`
object has a field, `Timezone.Timezone`, which is a string, and which
has to be at least three bytes long.
The other thing you'll have to look at is the `restapi/operations`
folder. In here you'll find GET and POST operations, the parameters
they accept, the responses they deliver, and lots of functions only
Swagger cares about. But there are a few we care about.
Here's how we craft the GET response. Inside `handlers.go`, you're
going to need to extract the requested timezone, get the time of day,
and then return either a success message or an error message. Looking
in the operations files, there are a methods for good and bad returns,
as we described in the swagger file.
```
2018-03-31 02:05:11 +00:00
<<gettime implementation>>=
func GetTime(params operations.TimeGetParams) middleware.Responder {
var tz *string = nil
if (params.Timezone != nil) {
tz = params.Timezone
}
thetime, err := getTimeOfDay(params.Timezone)
@
```
2018-03-31 02:05:11 +00:00
The first thing to notice here is the `params` field: we're getting a
customized, tightly bound object from the server. There's no hope of
abstraction here. The next is that we made the Timezone input optional,
so here we have to check if it's `nil` or not. if it isn't, we need to
set it. We do this here because we need to *cast* params.Timezone into
a pointer to a string, because Go is weird about types.
We then call a (thus far undefined) function called `getTimeOfDay`.
Let's deal with the error case:
```
2018-03-31 02:05:11 +00:00
<<gettime implementation>>=
if err != nil {
return operations.NewTimeGetNotFound().WithPayload(
&models.ErrorResponse {
int32(operations.TimeGetNotFoundCode),
swag.String(fmt.Sprintf("%s", err)),
})
}
@
```
2018-03-31 02:05:11 +00:00
That's a lot of references. We have a model, an operation, and what's
that "swag" thing? In order to satisfy Swagger's strictness, we use
only what Swagger offers: for our 404 case, we didn't find the timezone
requested, so we're returning the ErrorResponse model populated with a
numeric code and a string, extracted via `fmt`, from the err returned
from our time function. The 404 case for get is called, yes,
`NewClockGetNotFound`, and then `WithPayload()` decorates the body of
the response with content.
The good path is similar:
```
2018-03-31 02:05:11 +00:00
<<gettime implementation>>=
return operations.NewClockGetOK().WithPayload(
&models.Timeofday{
Timeofday: *thetime,
})
}
@
```
2018-03-31 02:05:11 +00:00
Now might be a good time to go look in `models/` and `/restapi/options`,
to see what's available to you. You'll need to do so anyway, because
unless you go to the
[git repository](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0)
and cheat, I'm going to leave it up to you to implement the PostTime().
There's still one thing missing, though: the actual time of day. We'll
need a default, and we'll need to test to see if the default is needed.
The implementation is straightforward:
```
<<timeofday function>>=
func getTimeOfDay(tz *string) (*string, error) {
defaultTZ := "UTC"
t := time.Now()
if tz == nil {
tz = &defaultTZ
}
utc, err := time.LoadLocation(*tz)
if err != nil {
return nil, errors.New(fmt.Sprintf("Time zone not found: %s", *tz))
}
thetime := t.In(utc).String()
return &thetime, nil
}
@
```
Now, if you've written everything correctly, and the compiler admits
that you have (or you can cheat and download the 0.2.0-tagged version
from the the repo), you'll be able to build, compile, and run the
server, and see it working:
```
$ go build ./cmd/timeofday-server/
$ ./timeofday-server --port=8080
```
And then test it with curl:
```
$ curl 'http://localhost:8020/timeofday/v1/time'
{"timeofday":"2018-03-31 02:57:48.814683375 +0000 UTC"}
$ curl 'http://localhost:8020/timeofday/v1/time?timezone=UTC'
{"timeofday":"2018-03-31 02:57:50.443200906 +0000 UTC"}
$ curl 'http://localhost:8020/timeofday/v1/time?timezone=America/Los_Angeles'
{"timeofday":"2018-03-30 19:57:59.886650128 -0700 PDT"}
```
And that's the end of Part 2. If you've gotten this far,
congratulations! Just a reminder, a working version of this server is
available under the "0.2.0" tag
[at the repo](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0).
On to [Part 3](TK)