-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat: Deep connector MS Dynamics365 Sales (Read) #157
Conversation
a1de53f
to
779f196
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RajatPawar, here is an initial explanation and questions to existing/future code changes
@@ -31,3 +31,14 @@ func InterpretError(res *http.Response, body []byte) error { | |||
|
|||
return NewHTTPStatusError(res.StatusCode, fmt.Errorf("%w: %s", ErrUnknown, string(body))) | |||
} | |||
|
|||
func PanicRecovery(wrapup func(cause error)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every connector captures panic errors and returns it as normal error. Wrapup is a callback after recovery
} | ||
|
||
if h.JSON != nil && mediaType == "application/json" { | ||
return h.JSON(res, body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on a ContentType a connector can parse error response in JSON, XML, HTML whatever impl we want to provide.
Every connector understands JSON structure in their own way. Other media types could be added.
// empty response body should not be parsed as JSON since it will cause ajson to err | ||
if len(body) == 0 { | ||
return nil, nil //nolint:nilnil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like every CRUD operation does this check (except GET, could be a bug), pulled it to this method since all of them call it at the end.
// ParamAssurance checks that param data is valid | ||
// Every param instance must implement it. | ||
type ParamAssurance interface { | ||
ValidateParams() error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several parameters with single responsibilities: Client/Workspace/Module. There could be more of these shared parameters.
Every parameter must determine if its value is required/optional by implementing ParamAssurance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very cool package, thanks for introducing it!
common/parse.go
Outdated
|
||
// JSONManager is a helpful wrapper of ajson library that adds errors when querying JSON payload | ||
// and provides common conversion methods. | ||
JSONManager = jsonManager{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Querying JSON content deals with finding "keys" and validating expected data type of the "value".
ErrNotArray, ErrNotObject errors can be universally shared for all JSON parsers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great idea!
msdsales/params.go
Outdated
opt(params) | ||
} | ||
|
||
return params, params.ValidateParams() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a construcotr of connector parameters from Option functions
with required field validation
msdsales/parse.go
Outdated
// * singular record | ||
// * list of records | ||
// * hybrid, list of records with extra fields describing list. | ||
func getMarshaledData(records []map[string]interface{}, fields []string) ([]common.ReadResultRow, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we marshal singular record in case of
GET domain/cats/<catID>
vs GET domain/cats
Also LIST of cats could return additional parameters stored outside of array, How those fields are returned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, our connector only does LIST and that's ok. We don't need to support the GET use case yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove the FIXME comment. I guess for future I was wondering if fields could perform queries like: [*].name
, [*].meta.createdAt
, listType
, expectedNumRecords
.
msdsales/read.go
Outdated
// $filter = query functions, comparisons | ||
// TODO pagination | ||
// $top = <int> of entries to return (ignored if header <Prefer: odata.maxpagesize>) | ||
// $count = counts all existing rows (@odata.count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Microsoft's Read is very flexible, see options. Should we support all query capabilities?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out @Cobalt0s! For now, we will just support the basic functionality of querying by field names, but this is really great to know. Can you replace the comments on line 13 to 24 with something like:
// Microsoft API supports other capabilities like filtering, grouping, and sorting which we can potentially tap into later.
// See https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/query-data-web-api#odata-query-options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, bottom line is I have described these options in docs.
test/msdsales/paginate/main.go
Outdated
} | ||
|
||
res, err = conn.Read(ctx, common.ReadParams{ | ||
NextPage: string(res.NextPage), // TODO should Read params have NextPageToken type? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ReadParams.NextPage(string) <- doesn't match type with ->
ReadResult.NextPage(NextPageToken)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch, can you update the ReadParams
struct in common/types.go to have NextPage
be of the type NextPageToken
token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
In places where it was expected to use as string instead of direct conversion I added a method. Let me know if it is not ok.
type NextPageToken string
func (t NextPageToken) String() string {
return string(t)
}
test/msdsales/read/main.go
Outdated
"github.com/amp-labs/connectors/test/utils" | ||
) | ||
|
||
func main() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common/reqrepeater/standard.go
Outdated
// Strategy is configuration of how a connector handles faulty requests | ||
// connector will create Retry based on this config every time it attempts to make API call. | ||
type Strategy interface { | ||
Start() Retry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
msdsales/params.go
Outdated
type parameters struct { | ||
paramsbuilder.Client | ||
paramsbuilder.Workspace | ||
paramsbuilder.Module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common/parse.go
Outdated
|
||
// JSONManager is a helpful wrapper of ajson library that adds errors when querying JSON payload | ||
// and provides common conversion methods. | ||
JSONManager = jsonManager{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great idea!
common/reqrepeater/standard.go
Outdated
// Strategy is configuration of how a connector handles faulty requests | ||
// connector will create Retry based on this config every time it attempts to make API call. | ||
type Strategy interface { | ||
Start() Retry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @Cobalt0s while this is a really good idea, we actually already handle retries in the server. We're almost done with building a sophisticated rate limiter that handles retries and employs different retry intervals according to the provider and other overrides that can be builder or consumer configurable. The overall design principle we're using is that the Connectors library handles making API calls & result + error parsing, while orchestration is handled by the server. So can you please remove the reqrepeater package?
msdsales/read_test.go
Outdated
"garbage": {} | ||
}`) | ||
})), | ||
expectedErrs: []error{errors.New("wrong request: wrong key 'value'")}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard coding a particular error string to expect leads to a fragile test case because if we modified our error message later, this test will break. The test case right above is OK since the mock server returns a specific error string, so it's ok to hardcode that in expectedErrs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is a smell code. There was another test where I used type comparison. To be more general I added reflection check. For this test checking that error comes from ajson
lib should be enough.
expectedErrs - used when you know exact error
expectedErrTypes - used less frequently and relies on type rather than content
test/msdsales/paginate/main.go
Outdated
} | ||
|
||
res, err = conn.Read(ctx, common.ReadParams{ | ||
NextPage: string(res.NextPage), // TODO should Read params have NextPageToken type? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great catch, can you update the ReadParams
struct in common/types.go to have NextPage
be of the type NextPageToken
token?
// ParamAssurance checks that param data is valid | ||
// Every param instance must implement it. | ||
type ParamAssurance interface { | ||
ValidateParams() error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very cool package, thanks for introducing it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much!
Closes #186