Skip to content
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1a7778c
adding test for custom entity
seankane-msft Jul 1, 2021
9216c9a
further
seankane-msft Jul 1, 2021
6988b97
starting access policy
seankane-msft Jul 2, 2021
e00b847
working for basic entity
seankane-msft Jul 7, 2021
0a9de3e
adding to test
seankane-msft Jul 8, 2021
cf111ad
Merge branch 'track2-tables' into tables
seankane-msft Jul 8, 2021
b467bef
Merge branch 'tables' of https://github.com/seankane-msft/azure-sdk-f…
seankane-msft Jul 8, 2021
9d6d3cd
new generated code, removed one more test
seankane-msft Jul 13, 2021
1e9042c
AddEntity now takes []bytes
seankane-msft Jul 13, 2021
433d302
adding directive to change entity to []byte, changes to compile and b…
seankane-msft Jul 13, 2021
9f59017
converting Value map[string]interface{} -> []byte in convenience layer
seankane-msft Jul 14, 2021
3b100c7
paging works the same way now too
seankane-msft Jul 14, 2021
3637273
fixing two tests, update complex and basic test entities, need to add…
seankane-msft Jul 14, 2021
184d60c
adding test for delete method, changed delete etag to be nil
seankane-msft Jul 14, 2021
85b1943
working test for merge
seankane-msft Jul 14, 2021
aae807a
Added Merge test
seankane-msft Jul 14, 2021
bf2e85c
fix up test query simple entity
seankane-msft Jul 14, 2021
7ed57ef
complex query test
seankane-msft Jul 14, 2021
4d70fc3
everything except for batch tests
seankane-msft Jul 14, 2021
f55a33a
basic batch functionality
seankane-msft Jul 14, 2021
98586c2
fixing batch mixed test
seankane-msft Jul 14, 2021
5542b1c
fixed last batch test
seankane-msft Jul 14, 2021
2568979
added separate file for testing batch actions
seankane-msft Jul 14, 2021
3a00da7
fixing query on table service client to take a pointer as well
seankane-msft Jul 14, 2021
ca87c68
working on annotating entities
seankane-msft Jul 15, 2021
c1781eb
fixing recordings
seankane-msft Jul 15, 2021
0084e9d
working with odata annotations
seankane-msft Jul 15, 2021
24baae9
working test for using odata to annotate entity
seankane-msft Jul 15, 2021
ad7581b
adding options to get
seankane-msft Jul 15, 2021
ba7a5af
working with embedded entity now too
seankane-msft Jul 16, 2021
bd2d060
adding to entity, peeling off prints
seankane-msft Jul 16, 2021
b9d63eb
converted to a type
seankane-msft Jul 16, 2021
2f26127
first change to add EdmEntity type
seankane-msft Jul 19, 2021
aa02460
updated unmarshalling
seankane-msft Jul 19, 2021
182dce8
removing two files
seankane-msft Jul 19, 2021
f5a2d24
fix editLink
seankane-msft Jul 19, 2021
fb52b18
removing sample
seankane-msft Jul 22, 2021
f7bec11
cleaning up
seankane-msft Jul 22, 2021
8278d31
merge conflicts
seankane-msft Jul 22, 2021
b4d078a
removing more code
seankane-msft Jul 22, 2021
b5c99dc
cleaning up
seankane-msft Jul 22, 2021
9ba729f
formatting
seankane-msft Jul 22, 2021
fab8d70
formatting
seankane-msft Jul 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions sdk/tables/autorest.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
129 changes: 129 additions & 0 deletions sdk/tables/aztable/byte_array_response.go
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 {
Copy link
Member

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?

Copy link
Contributor Author

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.Unmarshal functionality on recieved entities.

// 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
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I agree. azcore should have an etag type. @jhendrixMSFT

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
}
186 changes: 186 additions & 0 deletions sdk/tables/aztable/entity.go
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
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

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

json.Unmarshal converts all numbers to float64 by default.

Copy link
Member

Choose a reason for hiding this comment

The 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
}
Loading