2018-03-31 02:05:11 +00:00
|
|
|
# Review of Part One
|
|
|
|
|
2018-03-31 05:26:12 +00:00
|
|
|
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
|
2018-03-31 05:26:12 +00:00
|
|
|
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.
|
|
|
|
|
2018-03-31 05:26:12 +00:00
|
|
|
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
|
2018-03-31 05:26:12 +00:00
|
|
|
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
|
2018-03-31 05:26:12 +00:00
|
|
|
`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
|
2018-03-31 05:26:12 +00:00
|
|
|
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 03:00:15 +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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
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 03:00:15 +00:00
|
|
|
```
|
2018-03-31 02:05:11 +00:00
|
|
|
<<gettime implementation>>=
|
|
|
|
return operations.NewClockGetOK().WithPayload(
|
|
|
|
&models.Timeofday{
|
|
|
|
Timeofday: *thetime,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
@
|
2018-03-31 03:00:15 +00:00
|
|
|
```
|
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
|
2018-03-31 05:26:12 +00:00
|
|
|
[git repository](https://github.com/elfsternberg/go-swagger-tutorial/tree/0.2.0)
|
2018-03-31 03:00:15 +00:00
|
|
|
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,
|
2018-03-31 05:26:12 +00:00
|
|
|
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)
|
2018-03-31 03:00:15 +00:00
|
|
|
|