Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review matthieu #4

Closed
wants to merge 2 commits into from
Closed

Review matthieu #4

wants to merge 2 commits into from

Conversation

adolfoportilla
Copy link
Contributor

No description provided.

Adolfo Portilla added 2 commits September 30, 2019 17:18
@adolfoportilla adolfoportilla changed the base branch from master to DO-NOT-MERGE October 1, 2019 00:23
Copy link

@mtth mtth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool! I didn't look too closely into the code yet, mostly high level comments and suggestions to start.

// MakeBypass uses a make to bypass the Smartcar Connect brand selector.
// Smartcar Pro feature.
type MakeBypass struct {
Make string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider introducing types to avoid having "stringly-typed" signatures:

type Make string
type Scope string
// ...

This makes call-sites more explicit and less error-prone, especially when multiple such types are used together.

When the number of valid values is small (e.g. for UnitSystem), it is also common to expose the values themselves and use a different underlying type for additional safety:

type UnitSystem int

const (
  Metric UnitSystem = iota
  Imperial
)

However one downside of a non-string-based type is that serialization becomes less straightforward.

@@ -0,0 +1,20 @@
package constants
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very familiar with open-source Go but it doesn't look like using many nested packages is common. To keep things lightweight, I would recommend keeping the hierarchy as flat as possible, possibly even in the same folder (from taking a quick look at popular packages, many have 20+ files in a given folder, e.g. pg).

Additionally, I would recommend introducing internal packages only if they have a thinner public API than their implementation. When this isn't the case (e.g. here, where everything is public), you don't benefit from the package boundary. Having too many distinct packages has a cost on maintenance.

func Request(method, url, authorization, unitSystem string, body io.Reader) (http.Response, error) {

// Build Request
req, reqErr := http.NewRequest(method, url, body)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good practice for non-trivial API methods (e.g. those which make HTTP requests, database queries, remote calls, ...) to take in a context.Context as first argument. This allows the client to cleanly control cancellations, timeouts, etc. I strongly recommend following this pattern in the SDK. For this particular call, take a look at http.NewRequestWithContext.

)

// GetUserID returns the user ID of the vehicle owner associated with the accessToken.
func GetUserID(accessToken string) (string, error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several methods have a Get prefix (GetUserID, GetVehicleIds, ...), others don't (vehicle.Info, vehicle.VIN`). Can you make them consistent?

```
5. Construct vehicle
```go
vehicle := smartcar.Vehicle{ID: vehicleIds[0], AccessToken: token.Access}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend using a constructor for this, so that you can keep the ID field internal and immutable:

func NewVehicle(id, token string) *Vehicle

Otherwise you should document exactly what happens when the ID is mutated (e.g. concurrently with requests) or that it isn't allowed (but then you might as well guarantee it via the type system).

```
6. Send request to vehicle
```go
// Vehicle Endpoints
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API suggestion in case you have a batch endpoint (or plan on having one in the future) for "getter" methods:

type Key int

const (
  Info Key = iota
  VIN
  // ...
)

type Data struct {
  Info *Info // Only populated if the Info key was present
  VIN int // Only populated if the VIN key was present
  // ...
}

func (v *Vehicle) Data(keys ...Key) (*Data, error)

```go
vehicle := smartcar.Vehicle{ID: vehicleIds[0], AccessToken: token.Access}
```
6. Send request to vehicle
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might have missed it but I didn't see any specific handling below for expired tokens. As an API user, I would likely want to handle the case of an expired token error differently than others (e.g. to trigger a refresh). One simple option to enable this (without pattern matching on error strings) is to introduce a variable for this:

var ErrExpiredToken = errors.New("expired token")

Alternatively you could introduce a custom type, but that might not be worth it unless you want to add functionality (e.g. formatting or convenience for refreshing the token).

In any case, you should document what happens here :).

@adolfoportilla
Copy link
Contributor Author

Closing PR because it was only created to be reviewed by matthieu

@adolfoportilla adolfoportilla deleted the review-matthieu branch January 17, 2020 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants