Skip to content

Commit

Permalink
Fix unmarshalling rest servers (#292)
Browse files Browse the repository at this point in the history
In converting the rest servers settings to interfaces I ended up messing
with the ability to unmarshal YAML (or any other text serialization,
actually). This fixes that bug by indirecting the unmarshalling activity
to the concrete Settings implementation. This code looks a bit weird in
my opinion. Suggestions to make it better are welcome. The key factor
is: we cannot unmarshall into an interface without applying some kind of
reflection or type-tag switching.

Current main:

```
go run .\mocks\storeserver\storeserver\main.go .\defaults.yaml
panic: interface conversion: restserver.Settings is map[interface {}]interface {}, not storemockserver.Settings

goroutine 1 [running]:
main.serverFactory({0x85d780?, 0xc00003d170?})
        D:/p4w/hide-winrt/mocks/storeserver/storeserver/main.go:13 +0xe7
github.com/canonical/ubuntu-pro-for-windows/mocks/restserver.(*App).rootCmd.func2(0xc000004300?, {0xc000040350, 0x1, 0x8beb60?})
        D:/p4w/hide-winrt/mocks/restserver/application.go:152 +0x228
github.com/spf13/cobra.(*Command).execute(0xc000004300, {0xc00006e3b0, 0x1, 0x1})
```

With the proposed changes:

```
go run .\mocks\storeserver\storeserver\main.go .\defaults.yaml
Write 'exit' to stop serving
```

The first commit is unrelated to that bug, but I found it too small to
run the CI over it in a standalone PR.
The storemockserver would return an array of products as the top-level
entry in the JSON response for GET `/products`. Although valid JSON, the
MS JsonObject API didn't like that approach, it expects an object.
So, with that change, we return `{"products":["..."]}` instead of a
top-level array.
  • Loading branch information
CarlosNihelton authored Sep 22, 2023
2 parents f2d35e7 + be26755 commit 527fdb2
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ type Settings struct {
Subscription restserver.Endpoint
}

// Unmarshal tricks the type system so marshalling YAML will just work when called from the restserver.Settings interface.
func (s Settings) Unmarshal(in []byte, unmarshaller func(in []byte, out interface{}) (err error)) (restserver.Settings, error) {
err := unmarshaller(in, &s)
return s, err
}

// DefaultSettings returns the default set of settings for the server.
func DefaultSettings() Settings {
return Settings{
Expand Down
6 changes: 4 additions & 2 deletions mocks/restserver/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ type Server interface {
}

// Settings is the minimal interface a settings backend must provide to the Application.
type Settings interface{}
type Settings interface {
Unmarshal(in []byte, unmarshaller func(in []byte, out interface{}) (err error)) (Settings, error)
}

// App encapsulates creating and managing the CLI and lifecycle.
type App struct {
Expand Down Expand Up @@ -143,7 +145,7 @@ The outfile, if provided, will contain the address.`, app.Description),
os.Exit(1)
}

if err := yaml.Unmarshal(out, &settings); err != nil {
if settings, err = settings.Unmarshal(out, yaml.Unmarshal); err != nil {
slog.Error(fmt.Sprintf("Could not unmarshal settings: %v", err))
os.Exit(1)
}
Expand Down
8 changes: 7 additions & 1 deletion mocks/storeserver/storemockserver/storemockserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ type Settings struct {
AllProducts []Product
}

// Unmarshal tricks the type system so marshalling YAML will just work when called from the restserver.Settings interface.
func (s Settings) Unmarshal(in []byte, unmarshaller func(in []byte, out interface{}) (err error)) (restserver.Settings, error) {
err := unmarshaller(in, &s)
return s, err
}

// Server is a configurable mock of the MS Store runtime component that talks REST.
type Server struct {
restserver.ServerBase
Expand Down Expand Up @@ -226,7 +232,7 @@ func (s *Server) handleGetProducts(w http.ResponseWriter, r *http.Request) {
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprint(w, string(bs))
fmt.Fprintf(w, `{%q:%s}`, "products", string(bs))
}

func (s *Server) handlePurchase(w http.ResponseWriter, r *http.Request) {
Expand Down

0 comments on commit 527fdb2

Please sign in to comment.