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

Make API response accessible on returned API structs #1054

Merged
merged 4 commits into from
Apr 3, 2020

Commits on Apr 1, 2020

  1. Make API response accessible on returned API structs

    Makes an API response struct containing niceties like the raw response
    body, status, and request ID accessible via API resource structs
    returned from client functions. For example:
    
        customer, err := customer.New(params)
        fmt.Printf("request ID = %s\n", customer.LastResponse.RequestID)
    
    This is a feature that already exists in other language API libraries
    and which is requested occasionally here, usually for various situations
    involving more complex usage or desire for better observability.
    
    -- Implementation
    
    We introduce a few new types to make this work:
    
    * `APIResponse`: Represents a response from the Stripe API and includes
      things like request ID, status, and headers. I elected to create my
      own object instead of reusing `http.Response` because it gives us a
      little more flexibility, and hides many of myriad of fields exposed by
      the `http` version, which will hopefully give us a little more API
      stability/forward compatibility.
    
    * `APIResource`: A struct that contains `LastResponse` and is meant to
      represent any type that can we returned from a Stripe API endpoint. A
      coupon is an `APIResource` and so is a list object. This struct is
      embedded in response structs where appropriate across the whole API
      surface area (e.g. `Coupon`, `ListMeta`, etc.).
    
    * `LastResponseGetter`: A very basic interface to an object that looks
      like an `APIResource`. This isn't strictly necessary, but gives us
      slightly more flexibility around the API and makes backward
      compatibility a little bit better for non-standard use cases (see the
      section on that below).
    
    `stripe.Do` and other backend calls all start taking objects which are
    `LastResponseGetter` instead of `interface{}`. This provides us with some
    type safety around forgetting to include an embedded `APIResource` on
    structs that should have it by making the compiler balk.
    
    As `stripe.Do` finishes running a request, it generates an `APIResponse`
    object and sets it onto the API resource type it's deserializing and
    returning (e.g. a `Coupon`).
    
    Errors also embed `APIResource` and similarly get access to the same set
    of fields as response resources, although in their case some of the
    fields provided in `APIResponse` are duplicates of what they had
    already (see "Caveats" below).
    
    -- Backwards compatibility
    
    This is a minor breaking change in that backend implementations methods
    like `Do` now take `LastResponseGetter` instead of `interface{}`, which
    is more strict.
    
    The good news though is that:
    
    * Very few users should be using any of these even if they're
      technically public. The resource-specific clients packages tend to do
      all the work.
    
    * Users who are broken should have a very easy time updating code.
      Mostly this will just involve adding `APIResource` to structs that were
      being passed in.
    
    -- Naming
    
    * `APIResponse`: Went with this instead of `StripeResponse` as we see in
      some other libraries because the linter will complain that it
      "stutters" when used outside of the package (meaning, uses the same
      word twice in a row), i.e. `stripe.StripeResponse`. `APIResponse`
      sorts nicely with `APIResource` though, so I think it's okay.
    
    * `LastResponse`: Copied the "last" convention from other API libraries
      like stripe-python.
    
    * `LastResponseGetter`: Given an "-er" name per Go convention around
      small interfaces that are basically one liners -- e.g. `Reader`,
      `Writer, `Formatter`, `CloseNotifier`, etc. I can see the argument
      that this maybe should just be `APIResourceInterface` or something
      like that in case we start adding new things, but I figure at that
      point we can either rename it, or create a parent interface that
      encapsulates it:
    
        ``` go
        type APIResourceInterface interface {
            LastResponseGetter
        }
        ```
    
    -- Caveats
    
    * We only set the last response for top-level returned objects. For
      example, an `InvoiceItem` is an API resource, but if it's returned
      under an `Invoice`, only `Invoice` has a non-nil `LastResponse`. The
      same applies for all resources under list objects. I figure that doing
      it this way is more performant and makes a little bit more intuitive
      sense. Users should be able to work around it if they need to.
    
    * There is some duplication between `LastResponse` and some other fields
      that already existed on `stripe.Error` because the latter was already
      exposing some of this information, e.g. `RequestID`. I figure this is
      okay: it's nice that `stripe.Error` is a `LastResponseGetter` for
      consistency with other API resources. The duplication is a little
      unfortunate, but not that big of a deal.
    brandur committed Apr 1, 2020
    Configuration menu
    Copy the full SHA
    1a9ac85 View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    1e587df View commit details
    Browse the repository at this point in the history
  3. Update stripe.go

    Co-Authored-By: Olivier Bellone <ob@stripe.com>
    2 people authored and brandur committed Apr 1, 2020
    Configuration menu
    Copy the full SHA
    3544327 View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    826c6f5 View commit details
    Browse the repository at this point in the history