-
Notifications
You must be signed in to change notification settings - Fork 951
[Tables] Add support for Entity struct #15003
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
Changes from 42 commits
1a7778c
9216c9a
6988b97
e00b847
0a9de3e
cf111ad
b467bef
9d6d3cd
1e9042c
433d302
9f59017
3b100c7
3637273
184d60c
85b1943
aae807a
bf2e85c
7ed57ef
4d70fc3
f55a33a
98586c2
5542b1c
2568979
3a00da7
ca87c68
c1781eb
0084e9d
24baae9
ad7581b
ba7a5af
bd2d060
b9d63eb
2f26127
aa02460
182dce8
f5a2d24
fb52b18
f7bec11
8278d31
b4d078a
b5c99dc
9ba729f
fab8d70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,26 +13,12 @@ license-header: MICROSOFT_MIT_NO_VERSION | |
| clear-output-folder: false | ||
| output-folder: aztable | ||
| file-prefix: "zz_generated_" | ||
| # namespace: aztable | ||
| tag: package-2019-02 | ||
| credential-scope: none | ||
| use: "@autorest/[email protected]" | ||
| openapi-type: data-plane | ||
| ``` | ||
|
|
||
| <!-- This change will have to be made by hand for now, leaving this directive in case it is useful later --> | ||
| <!-- | ||
| ``` yaml | ||
| directive: | ||
| # dynamically change TableEntityProperties from map[string]interface{} to []byte | ||
| - from: swagger-document | ||
| where: $.definitions.TableEntityProperties | ||
| transform: >- | ||
| $["type"] = "string"; | ||
| $["format"] = "byte"; | ||
| delete $.additionalProperties; | ||
| ``` --> | ||
|
|
||
| ### Go multi-api | ||
|
|
||
| ``` yaml $(go) && $(multiapi) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package aztable | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "net/http" | ||
| "time" | ||
| ) | ||
|
|
||
| // ByteArrayResponse converts the MapOfInterfaceResponse.Value from a map[string]interface{} to a []byte | ||
| type ByteArrayResponse struct { | ||
| // ClientRequestID contains the information returned from the x-ms-client-request-id header response. | ||
| ClientRequestID *string | ||
|
|
||
| // ContentType contains the information returned from the Content-Type header response. | ||
| ContentType *string | ||
|
|
||
| // Date contains the information returned from the Date header response. | ||
| Date *time.Time | ||
|
|
||
| // ETag contains the information returned from the ETag header response. | ||
| ETag *string | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for future - in some of the other languages ETag is a type in Core. I wonder if we should do that eventually here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree. azcore should have an etag type. @jhendrixMSFT
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| // PreferenceApplied contains the information returned from the Preference-Applied header response. | ||
| PreferenceApplied *string | ||
|
|
||
| // RawResponse contains the underlying HTTP response. | ||
| RawResponse *http.Response | ||
|
|
||
| // RequestID contains the information returned from the x-ms-request-id header response. | ||
| RequestID *string | ||
|
|
||
| // The other properties of the table entity. | ||
| Value []byte | ||
|
|
||
| // Version contains the information returned from the x-ms-version header response. | ||
| Version *string | ||
|
|
||
| // XMSContinuationNextPartitionKey contains the information returned from the x-ms-continuation-NextPartitionKey header response. | ||
| XMSContinuationNextPartitionKey *string | ||
|
|
||
| // XMSContinuationNextRowKey contains the information returned from the x-ms-continuation-NextRowKey header response. | ||
| XMSContinuationNextRowKey *string | ||
| } | ||
|
|
||
| func newByteArrayResponse(m MapOfInterfaceResponse) (ByteArrayResponse, error) { | ||
| marshalledValue, err := json.Marshal(m.Value) | ||
| if err != nil { | ||
| return ByteArrayResponse{}, err | ||
| } | ||
| return ByteArrayResponse{ | ||
| ClientRequestID: m.ClientRequestID, | ||
| ContentType: m.ContentType, | ||
| Date: m.Date, | ||
| ETag: m.ETag, | ||
| PreferenceApplied: m.PreferenceApplied, | ||
| RawResponse: m.RawResponse, | ||
| RequestID: m.RequestID, | ||
| Value: marshalledValue, | ||
| Version: m.Version, | ||
| XMSContinuationNextPartitionKey: m.XMSContinuationNextPartitionKey, | ||
| XMSContinuationNextRowKey: m.XMSContinuationNextRowKey, | ||
| }, nil | ||
| } | ||
|
|
||
| // TableEntityQueryByteResponseResponse is the response envelope for operations that return a TableEntityQueryResponse type. | ||
| type TableEntityQueryByteResponseResponse struct { | ||
| // ClientRequestID contains the information returned from the x-ms-client-request-id header response. | ||
| ClientRequestID *string | ||
|
|
||
| // Date contains the information returned from the Date header response. | ||
| Date *time.Time | ||
|
|
||
| // RawResponse contains the underlying HTTP response. | ||
| RawResponse *http.Response | ||
|
|
||
| // RequestID contains the information returned from the x-ms-request-id header response. | ||
| RequestID *string | ||
|
|
||
| // The properties for the table entity query response. | ||
| TableEntityQueryResponse *TableEntityQueryByteResponse | ||
|
|
||
| // Version contains the information returned from the x-ms-version header response. | ||
| Version *string | ||
|
|
||
| // XMSContinuationNextPartitionKey contains the information returned from the x-ms-continuation-NextPartitionKey header response. | ||
| XMSContinuationNextPartitionKey *string | ||
|
|
||
| // XMSContinuationNextRowKey contains the information returned from the x-ms-continuation-NextRowKey header response. | ||
| XMSContinuationNextRowKey *string | ||
| } | ||
|
|
||
| // TableEntityQueryByteResponse - The properties for the table entity query response. | ||
| type TableEntityQueryByteResponse struct { | ||
| // The metadata response of the table. | ||
| OdataMetadata *string | ||
|
|
||
| // List of table entities. | ||
| Value [][]byte | ||
| } | ||
|
|
||
| func castToByteResponse(resp *TableEntityQueryResponseResponse) (TableEntityQueryByteResponseResponse, error) { | ||
| marshalledValue := make([][]byte, 0) | ||
| for _, e := range resp.TableEntityQueryResponse.Value { | ||
| m, err := json.Marshal(e) | ||
| if err != nil { | ||
| return TableEntityQueryByteResponseResponse{}, err | ||
| } | ||
| marshalledValue = append(marshalledValue, m) | ||
| } | ||
|
|
||
| t := TableEntityQueryByteResponse{ | ||
| OdataMetadata: resp.TableEntityQueryResponse.OdataMetadata, | ||
| Value: marshalledValue, | ||
| } | ||
|
|
||
| return TableEntityQueryByteResponseResponse{ | ||
| ClientRequestID: resp.ClientRequestID, | ||
| Date: resp.Date, | ||
| RawResponse: resp.RawResponse, | ||
| RequestID: resp.RequestID, | ||
| TableEntityQueryResponse: &t, | ||
| Version: resp.Version, | ||
| XMSContinuationNextPartitionKey: resp.XMSContinuationNextPartitionKey, | ||
| XMSContinuationNextRowKey: resp.XMSContinuationNextRowKey, | ||
| }, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package aztable | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "encoding/json" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
| ) | ||
|
|
||
| // https://docs.microsoft.com/en-us/rest/api/storageservices/payload-format-for-table-service-operations | ||
|
|
||
| type Entity struct { | ||
| PartitionKey string | ||
| RowKey string | ||
| Timestamp EdmDateTime | ||
| } | ||
|
|
||
| type EdmEntity struct { | ||
| Metadata string `json:"odata.metadata"` | ||
| Id string `json:"odata.id"` | ||
| EditLink string `json:"odata.editLink"` | ||
| Type string `json:"odata.type"` | ||
| Etag string `json:"odata.etag"` | ||
| Entity | ||
| Properties map[string]interface{} // Type assert the value to 1 of these: bool, int32, float64, string, EdmDateTime, EdmBinary, EdmGuid, EdmInt64 | ||
| } | ||
|
|
||
| func (e EdmEntity) MarshalJSON() ([]byte, error) { | ||
| entity := map[string]interface{}{} | ||
| entity["PartitionKey"], entity["RowKey"] = e.PartitionKey, e.RowKey | ||
|
|
||
| for propName, propValue := range e.Properties { | ||
| entity[propName] = propValue | ||
| edmType := "" | ||
| switch propValue.(type) { | ||
| case EdmDateTime: | ||
| edmType = "Edm.DateTime" | ||
| case EdmBinary: | ||
| edmType = "Edm.Binary" | ||
| case EdmGuid: | ||
| edmType = "Edm.Guid" | ||
| case EdmInt64: | ||
| edmType = "Edm.Int64" | ||
| } | ||
| if edmType != "" { | ||
| entity[propName+"@odata.type"] = edmType | ||
| } | ||
| } | ||
| return json.Marshal(entity) | ||
| } | ||
|
|
||
| func (e *EdmEntity) UnmarshalJSON(data []byte) (err error) { | ||
| var entity map[string]json.RawMessage | ||
| err = json.Unmarshal(data, &entity) | ||
| if err != nil { | ||
| return | ||
| } | ||
| e.Properties = map[string]interface{}{} | ||
| for propName, propRawValue := range entity { | ||
| if strings.Contains(propName, "@odata.type") { | ||
| continue // Skip the @odata.type properties; we look them up explicitly later | ||
| } | ||
| switch propName { | ||
| // Look for EdmEntity's specific fields first | ||
| case "odata.metadata": | ||
| err = json.Unmarshal(propRawValue, &e.Metadata) | ||
| case "odata.id": | ||
| err = json.Unmarshal(propRawValue, &e.Id) | ||
| case "odata.editLink": | ||
| err = json.Unmarshal(propRawValue, &e.EditLink) | ||
| case "odata.type": | ||
| err = json.Unmarshal(propRawValue, &e.Type) | ||
| case "odata.etag": | ||
| err = json.Unmarshal(propRawValue, &e.Etag) | ||
| case "PartitionKey": | ||
| err = json.Unmarshal(propRawValue, &e.PartitionKey) | ||
| case "RowKey": | ||
| err = json.Unmarshal(propRawValue, &e.RowKey) | ||
| case "Timestamp": | ||
| err = json.Unmarshal(propRawValue, &e.Timestamp) | ||
| default: | ||
| // Try to find the EDM type for this property & get it's value | ||
| var propertyEdmTypeValue string = "" | ||
| if propertyEdmTypeRawValue, ok := entity[propName+"@odata.type"]; ok { | ||
| if err = json.Unmarshal(propertyEdmTypeRawValue, &propertyEdmTypeValue); err != nil { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| var propValue interface{} = nil | ||
| switch propertyEdmTypeValue { | ||
| case "": // "<property>@odata.type" doesn't exist, infer the EDM type from the JSON type | ||
| // Try to unmarshal this property value as an int32 first | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should only try the int32 fallback if it deserialized as int64. My guess is that string will be more common.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The service sends int64 values as strings; not as numbers. If the @odata.type:Edm.Int64 is present then we have to convert a string to an int64 integer. If the Edm.Int64 is missing that an int64 will be a string. A number will either be int32 or float64. In other words, I think this is right as is.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My assumption was that this is to handle the default behavior of unmarshal, which makes all numbers Int64.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. json.Unmarshal converts all numbers to float64 by default.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right - that is what I assuming this logic was meant to deal with, so that we don't up-convert int32 values to Int64 on unmarshal. |
||
| var i32 int32 | ||
| if err = json.Unmarshal(propRawValue, &i32); err == nil { | ||
| propValue = i32 | ||
| } else { // Failed to parse number as an int32; unmarshal as usual | ||
| err = json.Unmarshal(propRawValue, &propValue) | ||
| } | ||
| case "Edm.DateTime": | ||
| var v EdmDateTime | ||
| err = json.Unmarshal(propRawValue, &v) | ||
| propValue = v | ||
| case "Edm.Binary": | ||
| var v EdmBinary | ||
| err = json.Unmarshal(propRawValue, &v) | ||
| propValue = v | ||
| case "Edm.Guid": | ||
| var v EdmGuid | ||
| err = json.Unmarshal(propRawValue, &v) | ||
| propValue = v | ||
| case "Edm.Int64": | ||
| var v EdmInt64 | ||
| err = json.Unmarshal(propRawValue, &v) | ||
| propValue = v | ||
| } | ||
| if err != nil { | ||
| return | ||
| } | ||
| e.Properties[propName] = propValue | ||
| } | ||
| } | ||
| return | ||
| } | ||
|
|
||
| type EdmBinary []byte | ||
|
|
||
| func (e EdmBinary) MarshalText() ([]byte, error) { | ||
| return ([]byte)(base64.StdEncoding.EncodeToString(([]byte)(e))), nil | ||
| } | ||
|
|
||
| func (e *EdmBinary) UnmarshalText(data []byte) error { | ||
| decoded, err := base64.StdEncoding.DecodeString(string(data)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *e = EdmBinary(decoded) | ||
| return nil | ||
| } | ||
|
|
||
| type EdmInt64 int64 | ||
|
|
||
| func (e EdmInt64) MarshalText() ([]byte, error) { | ||
| return []byte(strconv.FormatInt(int64(e), 10)), nil | ||
| } | ||
|
|
||
| func (e *EdmInt64) UnmarshalText(data []byte) error { | ||
| i, err := strconv.ParseInt(string(data), 10, 64) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *e = EdmInt64(i) | ||
| return nil | ||
| } | ||
|
|
||
| type EdmGuid string | ||
|
|
||
| func (e EdmGuid) MarshalText() ([]byte, error) { | ||
| return ([]byte)(e), nil | ||
| } | ||
|
|
||
| func (e *EdmGuid) UnmarshalText(data []byte) error { | ||
| *e = EdmGuid(string(data)) | ||
| return nil | ||
| } | ||
|
|
||
| type EdmDateTime time.Time | ||
|
|
||
| const rfc3339 = "2006-01-02T15:04:05.9999999Z" | ||
|
|
||
| func (e EdmDateTime) MarshalText() ([]byte, error) { | ||
| return ([]byte)(time.Time(e).Format(rfc3339)), nil | ||
| } | ||
|
|
||
| func (e *EdmDateTime) UnmarshalText(data []byte) error { | ||
| t, err := time.Parse(rfc3339, string(data)) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *e = EdmDateTime(t) | ||
| return nil | ||
| } | ||
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.
Is this an interim step to do this as part of generated code, or do we plan to always do this conversion?
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.
We always plan to do this conversion. This change gives the customer the abiltity to use the
json.Unmarshalfunctionality on recieved entities.