diff:
@@ -44,7 +44,10 @@ export default (props: any) => {
location: props.location,
}}
>
-

+

<div
id="head-container"
className="z-30 sticky top-0 dark:before:bg-gray-800 before:bg-white before:bg-opacity-[.85]

diff:
@@ -14,7 +14,10 @@ export default () => {

)}
{typeof Logo === 'function' && <Logo />}
-

+

{themeConfig.title}

original_message: feat(plugin-docs): Add id attribute of page container and header title (#684)


change_type: MODIFY

diff:
@@ -8,7 +8,7 @@ function Features(
props: PropsWithChildren<{ title?: string; subtitle?: string }>,
) {
return (
-

+

{(props.title || props.subtitle) && (

{props.title && (

license: MIT License

repo: umijs/umi-next

original_message: fix(plugin-docs): Fix home page horizontal scroll bar bug (#701)


message: fix(plugin-docs): Fix commented title still appears in TOC

diff:
export function parseTitle(opts: { content: string }) {
- const lines = opts.content.split('\n');
+ const lines = opts.content
+ .replace(/{[\n\s\t]*\/\*[\s\S]*?\*\/[\n\s\t]*}/g, '')
+ .split('\n');
let i = 0;
const ret = [];
while (i < lines.length) {

original_message: fix(plugin-docs): Fix #751 commented title still appears in TOC (#757)


diff:
@@ -95,9 +95,19 @@ export default () => {
className={cx(
'absolute transition-all duration-500 top-12 w-96 rounded-lg',
'cursor-pointer shadow overflow-hidden',
- result.length > 0 && isFocused ? 'max-h-80' : 'max-h-0',
+ keyword && isFocused ? 'max-h-80' : 'max-h-0',
)}
>
+ {keyword && result.length === 0 && (
+ <div>
+ <p
+ className="p-2 bg-white hover:bg-gray-50 transition
+ duration-300 group-focus:bg-blue-200 dark:bg-gray-700"
+ >
+ {render('No result for')} "{keyword}"
+ </p>
+ </div>
+ )}
{result.map((r, i) => (
<components.Link
to={(isFromPath ? currentLanguage?.locale : '') + r.href}

original_message: fix(plugin-docs): Show "No result" in search input (#766)


message: add waiting room status

diff:
@@ -29,6 +29,15 @@ type WaitingRoom struct {
JsonResponseEnabled bool `json:"json_response_enabled"`
}
+// WaitingRoomStatus describes the status of a waiting room.
+type WaitingRoomStatus struct {
+ Status string `json:"status"`
+ EventID string `json:"event_id"`
+ EstimatedQueuedUsers int `json:"estimated_queued_users"`
+ EstimatedTotalActiveUsers int `json:"estimated_total_active_users"`
+ MaxEstimatedTimeMinutes int `json:"max_estimated_time_minutes"`
+}
+
// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom.
type WaitingRoomDetailResponse struct {
Response
@@ -41,6 +50,12 @@ type WaitingRoomsResponse struct {
Result []WaitingRoom `json:"result"`
}
+// WaitingRoomStatusResponse is the API response, containing the status of a waiting room.
+type WaitingRoomStatusResponse struct {
+ Response
+ Result WaitingRoomStatus `json:"result"`
+}
+
// CreateWaitingRoom creates a new Waiting Room for a zone.
//
// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room
@@ -144,3 +159,20 @@ func (api *API) DeleteWaitingRoom(ctx context.Context, zoneID, waitingRoomID str
}
return nil
}
+
+// WaitingRoomStatus returns the status of one Waiting Room for a zone.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-get-waiting-room-status
+func (api *API) WaitingRoomStatus(ctx context.Context, zoneID, waitingRoomID string) (WaitingRoomStatus, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/status", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+ if err != nil {
+ return WaitingRoomStatus{}, err
+ }
+ var r WaitingRoomStatusResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomStatus{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}

diff:
@@ -32,6 +32,16 @@ var waitingRoomJSON = fmt.Sprintf(`
}
`, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano))
+var waitingRoomStatusJSON = fmt.Sprintf(`
+ {
+ "status": "queueing",
+ "event_id": "25756b2dfe6e378a06b033b670413757",
+ "estimated_queued_users": 10,
+ "estimated_total_active_users": 9,
+ "max_estimated_time_minutes": 5
+ }
+ `)
+
var waitingRoom = WaitingRoom{
ID: "699d98642c564d2e855e9661899b7252",
CreatedOn: testTimestampWaitingRoom,
@@ -50,6 +60,14 @@ var waitingRoom = WaitingRoom{
CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
}
+var waitingRoomStatus = WaitingRoomStatus{
+ Status: "queueing",
+ EventID: "25756b2dfe6e378a06b033b670413757",
+ EstimatedQueuedUsers: 10,
+ EstimatedTotalActiveUsers: 9,
+ MaxEstimatedTimeMinutes: 5,
+}
+
func TestListWaitingRooms(t *testing.T) {
setup()
defer teardown()
@@ -279,3 +297,28 @@ func TestDeleteWaitingRoom(t *testing.T) {
err := client.DeleteWaitingRoom(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252")
assert.NoError(t, err)
}
+
+func TestWaitingRoomStatus(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomStatusJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/status", handler)
+ want := waitingRoomStatus
+
+ actual, err := client.WaitingRoomStatus(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}

license: BSD 3-Clause New or Revised License

repo: cloudflare/cloudflare-go

original_message: add waiting room status


message: remove unnecessary fmt.Sprintf

diff:
@@ -32,7 +32,7 @@ var waitingRoomJSON = fmt.Sprintf(`
}
`, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano))
-var waitingRoomStatusJSON = fmt.Sprintf(`
+var waitingRoomStatusJSON = `
{
"status": "queueing",
"event_id": "25756b2dfe6e378a06b033b670413757",
@@ -40,7 +40,7 @@ var waitingRoomStatusJSON = fmt.Sprintf(`
"estimated_total_active_users": 9,
"max_estimated_time_minutes": 5
}
- `)
+ `
var waitingRoom = WaitingRoom{
ID: "699d98642c564d2e855e9661899b7252",

original_message: remove unnecessary fmt.Sprintf


message: add waiting room event methods

diff:
@@ -38,6 +38,26 @@ type WaitingRoomStatus struct {
MaxEstimatedTimeMinutes int `json:"max_estimated_time_minutes"`
}
+// WaitingRoomEvent describes a WaitingRoomEvent object.
+type WaitingRoomEvent struct {
+ ID string `json:"id,omitempty"`
+ CreatedOn time.Time `json:"created_on,omitempty"`
+ ModifiedOn time.Time `json:"modified_on,omitempty"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Suspended bool `json:"suspended"`
+ PrequeueStartTime time.Time `json:"prequeue_start_time,omitempty"`
+ EventStartTime time.Time `json:"event_start_time"`
+ EventEndTime time.Time `json:"event_end_time"`
+ ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
+ NewUsersPerMinute int `json:"new_users_per_minute,omitempty"`
+ TotalActiveUsers int `json:"total_active_users,omitempty"`
+ SessionDuration int `json:"session_duration,omitempty"`
+ DisableSessionRenewal bool `json:"disable_session_renewal,omitempty"`
+ QueueingMethod string `json:"queueing_method,omitempty"`
+ CustomPageHTML string `json:"custom_page_html,omitempty"`
+}
+
// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom.
type WaitingRoomDetailResponse struct {
Response
@@ -56,6 +76,18 @@ type WaitingRoomStatusResponse struct {
Result WaitingRoomStatus `json:"result"`
}
+// WaitingRoomEventDetailResponse is the API response, containing a single WaitingRoomEvent.
+type WaitingRoomEventDetailResponse struct {
+ Response
+ Result WaitingRoomEvent `json:"result"`
+}
+
+// WaitingRoomEventsResponse is the API response, containing an array of WaitingRoomEvents.
+type WaitingRoomEventsResponse struct {
+ Response
+ Result []WaitingRoomEvent `json:"result"`
+}
+
// CreateWaitingRoom creates a new Waiting Room for a zone.
//
// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room
@@ -176,3 +208,107 @@ func (api *API) WaitingRoomStatus(ctx context.Context, zoneID, waitingRoomID str
}
return r.Result, nil
}
+
+// CreateWaitingRoomEvent creates a new event for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-create-event
+func (api *API) CreateWaitingRoomEvent(ctx context.Context, zoneID string, waitingRoomID string, waitingRoomEvent WaitingRoomEvent) (*WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodPost, uri, waitingRoomEvent)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, errors.Wrap(err, errUnmarshalError)
+ }
+ return &r.Result, nil
+}
+
+// ListWaitingRoomEvents returns all Waiting Room Events for a zone.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-list-events
+func (api *API) ListWaitingRoomEvents(ctx context.Context, zoneID string, waitingRoomID string) ([]WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+ if err != nil {
+ return []WaitingRoomEvent{}, err
+ }
+ var r WaitingRoomEventsResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return []WaitingRoomEvent{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
+// WaitingRoomEvent fetches detail about one Waiting Room Event for a zone.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-event-details
+func (api *API) WaitingRoomEvent(ctx context.Context, zoneID string, waitingRoomID string, eventID string) (WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events/%s", zoneID, waitingRoomID, eventID)
+ res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+ if err != nil {
+ return WaitingRoomEvent{}, err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomEvent{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
+// ChangeWaitingRoomEvent lets you change individual settings for a Waiting Room Event. This is
+// in contrast to UpdateWaitingRoomEvent which replaces the entire Waiting Room Event.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-patch-event
+func (api *API) ChangeWaitingRoomEvent(ctx context.Context, zoneID, waitingRoomID string, waitingRoomEvent WaitingRoomEvent) (WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events/%s", zoneID, waitingRoomID, waitingRoomEvent.ID)
+ res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, waitingRoomEvent)
+ if err != nil {
+ return WaitingRoomEvent{}, err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomEvent{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
+// UpdateWaitingRoomEvent lets you replace a Waiting Room Event. This is in contrast to
+// ChangeWaitingRoomEvent which lets you change individual settings.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-update-event
+func (api *API) UpdateWaitingRoomEvent(ctx context.Context, zoneID string, waitingRoomID string, waitingRoomEvent WaitingRoomEvent) (WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events/%s", zoneID, waitingRoomID, waitingRoomEvent.ID)
+ res, err := api.makeRequestContext(ctx, http.MethodPut, uri, waitingRoomEvent)
+ if err != nil {
+ return WaitingRoomEvent{}, err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomEvent{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
+// DeleteWaitingRoomEvent deletes an event for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-delete-event
+func (api *API) DeleteWaitingRoomEvent(ctx context.Context, zoneID string, waitingRoomID string, eventID string) error {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events/%s", zoneID, waitingRoomID, eventID)
+ res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+ if err != nil {
+ return err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return errors.Wrap(err, errUnmarshalError)
+ }
+ return nil
+}

original_message: add waiting room event methods


message: add waiting room event tests

diff:
@@ -11,10 +11,16 @@ import (
"github.com/stretchr/testify/assert"
)
+var waitingRoomID = "699d98642c564d2e855e9661899b7252"
+var waitingRoomEventID = "25756b2dfe6e378a06b033b670413757"
var testTimestampWaitingRoom = time.Now().UTC()
+var testTimestampWaitingRoomEvent = time.Now().UTC()
+var testTimestampWaitingRoomEventPrequeue = time.Now().UTC()
+var testTimestampWaitingRoomEventStart = testTimestampWaitingRoomEventPrequeue.Add(5 * time.Minute)
+var testTimestampWaitingRoomEventEnd = testTimestampWaitingRoomEventStart.Add(1 * time.Minute)
var waitingRoomJSON = fmt.Sprintf(`
{
- "id": "699d98642c564d2e855e9661899b7252",
+ "id": "%s",
"created_on": "%s",
"modified_on": "%s",
"name": "production_webinar",
@@ -30,20 +36,45 @@ var waitingRoomJSON = fmt.Sprintf(`
"json_response_enabled": true,
"custom_page_html": "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}"
}
- `, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano))
+ `, waitingRoomID, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano))
-var waitingRoomStatusJSON = `
+var waitingRoomEventJSON = fmt.Sprintf(`
+ {
+ "id": "%s",
+ "created_on": "%s",
+ "modified_on": "%s",
+ "name": "production_webinar_event",
+ "description": "Production event - DO NOT MODIFY",
+ "suspended": false,
+ "prequeue_start_time": "%s",
+ "event_start_time": "%s",
+ "event_end_time": "%s",
+ "shuffle_at_event_start": false,
+ "new_users_per_minute": 2000,
+ "total_active_users": 2500,
+ "session_duration": null,
+ "disable_session_renewal": null,
+ "queueing_method": "random",
+ "custom_page_html": "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Event is prequeueing / Queue all enabled {{/waitTimeKnown}}"
+ }
+ `, waitingRoomEventID, testTimestampWaitingRoomEvent.Format(time.RFC3339Nano),
+ testTimestampWaitingRoomEvent.Format(time.RFC3339Nano),
+ testTimestampWaitingRoomEventPrequeue.Format(time.RFC3339Nano),
+ testTimestampWaitingRoomEventStart.Format(time.RFC3339Nano),
+ testTimestampWaitingRoomEventEnd.Format(time.RFC3339Nano))
+
+var waitingRoomStatusJSON = fmt.Sprintf(`
{
"status": "queueing",
- "event_id": "25756b2dfe6e378a06b033b670413757",
+ "event_id": "%s",
"estimated_queued_users": 10,
"estimated_total_active_users": 9,
"max_estimated_time_minutes": 5
}
- `
+ `, waitingRoomEventID)
var waitingRoom = WaitingRoom{
- ID: "699d98642c564d2e855e9661899b7252",
+ ID: waitingRoomID,
CreatedOn: testTimestampWaitingRoom,
ModifiedOn: testTimestampWaitingRoom,
Name: "production_webinar",
@@ -60,9 +91,28 @@ var waitingRoom = WaitingRoom{
CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
}
+var waitingRoomEvent = WaitingRoomEvent{
+ ID: waitingRoomEventID,
+ CreatedOn: testTimestampWaitingRoomEvent,
+ ModifiedOn: testTimestampWaitingRoomEvent,
+ Name: "production_webinar_event",
+ Description: "Production event - DO NOT MODIFY",
+ Suspended: false,
+ PrequeueStartTime: testTimestampWaitingRoomEventPrequeue,
+ EventStartTime: testTimestampWaitingRoomEventStart,
+ EventEndTime: testTimestampWaitingRoomEventEnd,
+ ShuffleAtEventStart: false,
+ NewUsersPerMinute: 2000,
+ TotalActiveUsers: 2500,
+ SessionDuration: 0,
+ DisableSessionRenewal: false,
+ QueueingMethod: "random",
+ CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Event is prequeueing / Queue all enabled {{/waitTimeKnown}}",
+}
+
var waitingRoomStatus = WaitingRoomStatus{
Status: "queueing",
- EventID: "25756b2dfe6e378a06b033b670413757",
+ EventID: waitingRoomEventID,
EstimatedQueuedUsers: 10,
EstimatedTotalActiveUsers: 9,
MaxEstimatedTimeMinutes: 5,
@@ -322,3 +372,179 @@ func TestWaitingRoomStatus(t *testing.T) {
assert.Equal(t, want, actual)
}
}
+
+func TestCreateWaitingRoomEvent(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events", handler)
+ want := &waitingRoomEvent
+
+ actual, err := client.CreateWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomEvent)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestListWaitingRoomEvents(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": [
+ %s
+ ]
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events", handler)
+ want := []WaitingRoomEvent{waitingRoomEvent}
+
+ actual, err := client.ListWaitingRoomEvents(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestListWaitingRoomEventsNoResult(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprint(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": []
+ }
+ `)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events", handler)
+ want := []WaitingRoomEvent{}
+
+ actual, err := client.ListWaitingRoomEvents(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestWaitingRoomEvent(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events/25756b2dfe6e378a06b033b670413757", handler)
+ want := waitingRoomEvent
+
+ actual, err := client.WaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", "25756b2dfe6e378a06b033b670413757")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestUpdateWaitingRoomEvent(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events/25756b2dfe6e378a06b033b670413757", handler)
+ want := waitingRoomEvent
+
+ actual, err := client.UpdateWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomEvent)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestChangeWaitingRoomEvent(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PATCH', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events/25756b2dfe6e378a06b033b670413757", handler)
+ want := waitingRoomEvent
+
+ actual, err := client.UpdateWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomEvent)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestDeleteWaitingRoomEvent(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprint(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": {
+ "id": "25756b2dfe6e378a06b033b670413757"
+ }
+ }
+ `)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events/25756b2dfe6e378a06b033b670413757", handler)
+
+ err := client.DeleteWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", "25756b2dfe6e378a06b033b670413757")
+ assert.NoError(t, err)
+}

original_message: add waiting room event tests


message: fix structslop errors

diff:
@@ -40,22 +40,22 @@ type WaitingRoomStatus struct {
// WaitingRoomEvent describes a WaitingRoomEvent object.
type WaitingRoomEvent struct {
- ID string `json:"id,omitempty"`
+ EventEndTime time.Time `json:"event_end_time"`
CreatedOn time.Time `json:"created_on,omitempty"`
ModifiedOn time.Time `json:"modified_on,omitempty"`
- Name string `json:"name"`
- Description string `json:"description,omitempty"`
- Suspended bool `json:"suspended"`
PrequeueStartTime time.Time `json:"prequeue_start_time,omitempty"`
EventStartTime time.Time `json:"event_start_time"`
- EventEndTime time.Time `json:"event_end_time"`
- ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ QueueingMethod string `json:"queueing_method,omitempty"`
+ ID string `json:"id,omitempty"`
+ CustomPageHTML string `json:"custom_page_html,omitempty"`
NewUsersPerMinute int `json:"new_users_per_minute,omitempty"`
TotalActiveUsers int `json:"total_active_users,omitempty"`
SessionDuration int `json:"session_duration,omitempty"`
DisableSessionRenewal bool `json:"disable_session_renewal,omitempty"`
- QueueingMethod string `json:"queueing_method,omitempty"`
- CustomPageHTML string `json:"custom_page_html,omitempty"`
+ Suspended bool `json:"suspended"`
+ ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
}
// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom.

original_message: fix structslop errors


message: fix falsey bool json marshalling

diff:
@@ -53,7 +53,7 @@ type WaitingRoomEvent struct {
NewUsersPerMinute int `json:"new_users_per_minute,omitempty"`
TotalActiveUsers int `json:"total_active_users,omitempty"`
SessionDuration int `json:"session_duration,omitempty"`
- DisableSessionRenewal bool `json:"disable_session_renewal,omitempty"`
+ DisableSessionRenewal *bool `json:"disable_session_renewal,omitempty"`
Suspended bool `json:"suspended"`
ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
}

diff:
@@ -105,7 +105,7 @@ var waitingRoomEvent = WaitingRoomEvent{
NewUsersPerMinute: 2000,
TotalActiveUsers: 2500,
SessionDuration: 0,
- DisableSessionRenewal: false,
+ DisableSessionRenewal: nil,
QueueingMethod: "random",
CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Event is prequeueing / Queue all enabled {{/waitTimeKnown}}",
}

original_message: fix falsey bool json marshalling


message: add waiting room event preview

diff:
@@ -260,6 +260,24 @@ func (api *API) WaitingRoomEvent(ctx context.Context, zoneID string, waitingRoom
return r.Result, nil
}
+// WaitingRoomEventPreview returns an event's configuration as if it was active.
+// Inherited fields from the waiting room will be displayed with their current values.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-preview-active-event-details
+func (api *API) WaitingRoomEventPreview(ctx context.Context, zoneID string, waitingRoomID string, eventID string) (WaitingRoomEvent, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/events/%s/details", zoneID, waitingRoomID, eventID)
+ res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+ if err != nil {
+ return WaitingRoomEvent{}, err
+ }
+ var r WaitingRoomEventDetailResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomEvent{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
// ChangeWaitingRoomEvent lets you change individual settings for a Waiting Room Event. This is
// in contrast to UpdateWaitingRoomEvent which replaces the entire Waiting Room Event.
//

diff:
@@ -475,6 +475,31 @@ func TestWaitingRoomEvent(t *testing.T) {
}
}
+func TestWaitingRoomEventPreview(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomEventJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/events/25756b2dfe6e378a06b033b670413757/details", handler)
+ want := waitingRoomEvent
+
+ actual, err := client.WaitingRoomEventPreview(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", "25756b2dfe6e378a06b033b670413757")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
func TestUpdateWaitingRoomEvent(t *testing.T) {
setup()
defer teardown()

original_message: add waiting room event preview


message: add waiting room custom page preview

diff:
@@ -58,6 +58,11 @@ type WaitingRoomEvent struct {
ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
}
+// WaitingRoomPagePreview describes a WaitingRoomPagePreview object.
+type WaitingRoomPagePreview struct {
+ PreviewURL string `json:"preview_url"`
+}
+
// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom.
type WaitingRoomDetailResponse struct {
Response
@@ -76,6 +81,12 @@ type WaitingRoomStatusResponse struct {
Result WaitingRoomStatus `json:"result"`
}
+// WaitingRoomPagePreviewResponse is the API response, containing the URL to a custom waiting room preview.
+type WaitingRoomPagePreviewResponse struct {
+ Response
+ Result WaitingRoomPagePreview `json:"result"`
+}
+
// WaitingRoomEventDetailResponse is the API response, containing a single WaitingRoomEvent.
type WaitingRoomEventDetailResponse struct {
Response
@@ -209,6 +220,29 @@ func (api *API) WaitingRoomStatus(ctx context.Context, zoneID, waitingRoomID str
return r.Result, nil
}
+// WaitingRoomPagePreview uploads a custom waiting room page for preview and
+// returns a preview URL.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-create-a-custom-waiting-room-page-preview
+func (api *API) WaitingRoomPagePreview(ctx context.Context, zoneID, customHTML string) (WaitingRoomPagePreview, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/preview", zoneID)
+ res, err := api.makeRequestContext(ctx, http.MethodPost, uri, struct {
+ CustomHTML string `json:"custom_html"`
+ }{
+ CustomHTML: customHTML,
+ })
+
+ if err != nil {
+ return WaitingRoomPagePreview{}, err
+ }
+ var r WaitingRoomPagePreviewResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return WaitingRoomPagePreview{}, errors.Wrap(err, errUnmarshalError)
+ }
+ return r.Result, nil
+}
+
// CreateWaitingRoomEvent creates a new event for a Waiting Room.
//
// API reference: https://api.cloudflare.com/#waiting-room-create-event

diff:
@@ -73,6 +73,12 @@ var waitingRoomStatusJSON = fmt.Sprintf(`
}
`, waitingRoomEventID)
+var waitingRoomPagePreviewJSON = `
+ {
+ "preview_url": "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb"
+ }
+ `
+
var waitingRoom = WaitingRoom{
ID: waitingRoomID,
CreatedOn: testTimestampWaitingRoom,
@@ -118,6 +124,10 @@ var waitingRoomStatus = WaitingRoomStatus{
MaxEstimatedTimeMinutes: 5,
}
+var waitingRoomPagePreview = WaitingRoomPagePreview{
+ PreviewURL: "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb",
+}
+
func TestListWaitingRooms(t *testing.T) {
setup()
defer teardown()
@@ -373,6 +383,31 @@ func TestWaitingRoomStatus(t *testing.T) {
}
}
+func TestWaitingRoomPagePreview(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": %s
+ }
+ `, waitingRoomPagePreviewJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/preview", handler)
+ want := waitingRoomPagePreview
+
+ actual, err := client.WaitingRoomPagePreview(context.Background(), testZoneID, "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
func TestCreateWaitingRoomEvent(t *testing.T) {
setup()
defer teardown()

original_message: add waiting room custom page preview


message: create CustomHTML struct and rename PreviewURL struct

diff:
@@ -58,11 +58,16 @@ type WaitingRoomEvent struct {
ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
}
-// WaitingRoomPagePreview describes a WaitingRoomPagePreview object.
-type WaitingRoomPagePreview struct {
+// WaitingRoomPagePreviewURL describes a WaitingRoomPagePreviewURL object.
+type WaitingRoomPagePreviewURL struct {
PreviewURL string `json:"preview_url"`
}
+// WaitingRoomPagePreviewCustomHTML describes a WaitingRoomPagePreviewCustomHTML object.
+type WaitingRoomPagePreviewCustomHTML struct {
+ CustomHTML string `json:"custom_html"`
+}
+
// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom.
type WaitingRoomDetailResponse struct {
Response
@@ -84,7 +89,7 @@ type WaitingRoomStatusResponse struct {
// WaitingRoomPagePreviewResponse is the API response, containing the URL to a custom waiting room preview.
type WaitingRoomPagePreviewResponse struct {
Response
- Result WaitingRoomPagePreview `json:"result"`
+ Result WaitingRoomPagePreviewURL `json:"result"`
}
// WaitingRoomEventDetailResponse is the API response, containing a single WaitingRoomEvent.
@@ -224,21 +229,17 @@ func (api *API) WaitingRoomStatus(ctx context.Context, zoneID, waitingRoomID str
// returns a preview URL.
//
// API reference: https://api.cloudflare.com/#waiting-room-create-a-custom-waiting-room-page-preview
-func (api *API) WaitingRoomPagePreview(ctx context.Context, zoneID, customHTML string) (WaitingRoomPagePreview, error) {
+func (api *API) WaitingRoomPagePreview(ctx context.Context, zoneID, customHTML string) (WaitingRoomPagePreviewURL, error) {
uri := fmt.Sprintf("/zones/%s/waiting_rooms/preview", zoneID)
- res, err := api.makeRequestContext(ctx, http.MethodPost, uri, struct {
- CustomHTML string `json:"custom_html"`
- }{
- CustomHTML: customHTML,
- })
+ res, err := api.makeRequestContext(ctx, http.MethodPost, uri, WaitingRoomPagePreviewCustomHTML{CustomHTML: customHTML})
if err != nil {
- return WaitingRoomPagePreview{}, err
+ return WaitingRoomPagePreviewURL{}, err
}
var r WaitingRoomPagePreviewResponse
err = json.Unmarshal(res, &r)
if err != nil {
- return WaitingRoomPagePreview{}, errors.Wrap(err, errUnmarshalError)
+ return WaitingRoomPagePreviewURL{}, errors.Wrap(err, errUnmarshalError)
}
return r.Result, nil
}

diff:
@@ -124,7 +124,7 @@ var waitingRoomStatus = WaitingRoomStatus{
MaxEstimatedTimeMinutes: 5,
}
-var waitingRoomPagePreview = WaitingRoomPagePreview{
+var waitingRoomPagePreview = WaitingRoomPagePreviewURL{
PreviewURL: "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb",
}


message: add healthcheck notification deprecation

diff:
@@ -52,6 +52,7 @@ type HealthcheckTCPConfig struct {
}
// HealthcheckNotification describes notification configuration for a healthcheck.
+// Deprecated: Use CreateNotificationPolicy() instead.
type HealthcheckNotification struct {
Suspended bool `json:"suspended,omitempty"`
EmailAddresses []string `json:"email_addresses,omitempty"`


message: add next_event_prequeue_start_time and next_event_start_time and use time pointer"

diff:
@@ -27,6 +27,8 @@ type WaitingRoom struct {
DisableSessionRenewal bool `json:"disable_session_renewal"`
Suspended bool `json:"suspended"`
JsonResponseEnabled bool `json:"json_response_enabled"`
+ NextEventPrequeueStartTime *time.Time `json:"next_event_prequeue_start_time,omitempty"`
+ NextEventStartTime *time.Time `json:"next_event_start_time,omitempty"`
}
// WaitingRoomStatus describes the status of a waiting room.
@@ -43,7 +45,7 @@ type WaitingRoomEvent struct {
EventEndTime time.Time `json:"event_end_time"`
CreatedOn time.Time `json:"created_on,omitempty"`
ModifiedOn time.Time `json:"modified_on,omitempty"`
- PrequeueStartTime time.Time `json:"prequeue_start_time,omitempty"`
+ PrequeueStartTime *time.Time `json:"prequeue_start_time,omitempty"`
EventStartTime time.Time `json:"event_start_time"`
Name string `json:"name"`
Description string `json:"description,omitempty"`

diff:
@@ -34,9 +34,12 @@ var waitingRoomJSON = fmt.Sprintf(`
"session_duration": 10,
"disable_session_renewal": false,
"json_response_enabled": true,
- "custom_page_html": "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}"
+ "custom_page_html": "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
+ "next_event_prequeue_start_time": null,
+ "next_event_start_time": "%s"
}
- `, waitingRoomID, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano))
+ `, waitingRoomID, testTimestampWaitingRoom.Format(time.RFC3339Nano), testTimestampWaitingRoom.Format(time.RFC3339Nano),
+ testTimestampWaitingRoomEventStart.Format(time.RFC3339Nano))
var waitingRoomEventJSON = fmt.Sprintf(`
{
@@ -95,6 +98,8 @@ var waitingRoom = WaitingRoom{
DisableSessionRenewal: false,
JsonResponseEnabled: true,
CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
+ NextEventStartTime: &testTimestampWaitingRoomEventStart,
+ NextEventPrequeueStartTime: nil,
}
var waitingRoomEvent = WaitingRoomEvent{
@@ -104,7 +109,7 @@ var waitingRoomEvent = WaitingRoomEvent{
Name: "production_webinar_event",
Description: "Production event - DO NOT MODIFY",
Suspended: false,
- PrequeueStartTime: testTimestampWaitingRoomEventPrequeue,
+ PrequeueStartTime: &testTimestampWaitingRoomEventPrequeue,
EventStartTime: testTimestampWaitingRoomEventStart,
EventEndTime: testTimestampWaitingRoomEventEnd,
ShuffleAtEventStart: false,


message: add WaitingRoom DefaultTemplateLanguage member

diff:
@@ -18,6 +18,7 @@ type WaitingRoom struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
CustomPageHTML string `json:"custom_page_html,omitempty"`
+ DefaultTemplateLanguage string `json:"default_template_language,omitempty"`
Host string `json:"host"`
ID string `json:"id,omitempty"`
NewUsersPerMinute int `json:"new_users_per_minute"`

diff:
@@ -35,6 +35,7 @@ var waitingRoomJSON = fmt.Sprintf(`
"disable_session_renewal": false,
"json_response_enabled": true,
"custom_page_html": "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
+ "default_template_language": "en-US",
"next_event_prequeue_start_time": null,
"next_event_start_time": "%s"
}
@@ -98,6 +99,7 @@ var waitingRoom = WaitingRoom{
DisableSessionRenewal: false,
JsonResponseEnabled: true,
CustomPageHTML: "{{#waitTimeKnown}} {{waitTime}} mins {{/waitTimeKnown}} {{^waitTimeKnown}} Queue all enabled {{/waitTimeKnown}}",
+ DefaultTemplateLanguage: "en-US",
NextEventStartTime: &testTimestampWaitingRoomEventStart,
NextEventPrequeueStartTime: nil,
}


message: remove healthcheck notification property

diff:
@@ -28,7 +28,6 @@ type Healthcheck struct {
CheckRegions []string `json:"check_regions"`
HTTPConfig *HealthcheckHTTPConfig `json:"http_config,omitempty"`
TCPConfig *HealthcheckTCPConfig `json:"tcp_config,omitempty"`
- Notification HealthcheckNotification `json:"notification,omitempty"`
Status string `json:"status"`
FailureReason string `json:"failure_reason"`
}
@@ -51,14 +50,6 @@ type HealthcheckTCPConfig struct {
Port uint16 `json:"port,omitempty"`
}
-// HealthcheckNotification describes notification configuration for a healthcheck.
-// Deprecated: Use CreateNotificationPolicy() instead.
-type HealthcheckNotification struct {
- Suspended bool `json:"suspended,omitempty"`
- EmailAddresses []string `json:"email_addresses,omitempty"`
- Trigger string `json:"trigger,omitempty"`
-}
-
// HealthcheckListResponse is the API response, containing an array of healthchecks.
type HealthcheckListResponse struct {
Response

diff:
@@ -45,11 +45,6 @@ const (
}
},
"tcp_config": null,
- "notification": {
- "suspended": false,
- "email_addresses": ["alerts@example.com"],
- "trigger": "ALL"
- },
"created_on": "2019-01-13T12:20:00.12345Z",
"modified_on": "2019-01-13T12:20:00.12345Z",
"status": "unknown",
@@ -88,11 +83,6 @@ var (
"Host": {"www.example.com"},
},
},
- Notification: HealthcheckNotification{
- Suspended: false,
- EmailAddresses: []string{"alerts@example.com"},
- Trigger: "ALL",
- },
Status: "unknown",
FailureReason: "",
}


message: add queueing_method to waiting room struct

diff:
@@ -17,6 +17,7 @@ type WaitingRoom struct {
Path string `json:"path"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
+ QueueingMethod string `json:"queueing_method,omitempty"`
CustomPageHTML string `json:"custom_page_html,omitempty"`
DefaultTemplateLanguage string `json:"default_template_language,omitempty"`
Host string `json:"host"`

diff:
@@ -25,6 +25,7 @@ var waitingRoomJSON = fmt.Sprintf(`
"modified_on": "%s",
"name": "production_webinar",
"description": "Production - DO NOT MODIFY",
+ "queueing_method": "random",
"suspended": false,
"host": "shop.example.com",
"path": "/shop/checkout",
@@ -89,6 +90,7 @@ var waitingRoom = WaitingRoom{
ModifiedOn: testTimestampWaitingRoom,
Name: "production_webinar",
Description: "Production - DO NOT MODIFY",
+ QueueingMethod: "random",
Suspended: false,
Host: "shop.example.com",
Path: "/shop/checkout",


message: add waitingroom rules

diff:
@@ -60,6 +60,16 @@ type WaitingRoomEvent struct {
ShuffleAtEventStart bool `json:"shuffle_at_event_start"`
}
+type WaitingRoomRule struct {
+ ID string `json:"id,omitempty"`
+ Version string `json:"version,omitempty"`
+ Action string `json:"action"`
+ Expression string `json:"expression"`
+ Description string `json:"description"`
+ LastUpdated *time.Time `json:"last_updated,omitempty"`
+ Enabled bool `json:"enabled"`
+}
+
// WaitingRoomPagePreviewURL describes a WaitingRoomPagePreviewURL object.
type WaitingRoomPagePreviewURL struct {
PreviewURL string `json:"preview_url"`
@@ -106,6 +116,12 @@ type WaitingRoomEventsResponse struct {
Result []WaitingRoomEvent `json:"result"`
}
+// WaitingRoomRulesResponse is the API response, containing an array of WaitingRoomRule.
+type WaitingRoomRulesResponse struct {
+ Response
+ Result []WaitingRoomRule `json:"result"`
+}
+
// CreateWaitingRoom creates a new Waiting Room for a zone.
//
// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room
@@ -367,3 +383,88 @@ func (api *API) DeleteWaitingRoomEvent(ctx context.Context, zoneID string, waiti
}
return nil
}
+
+// ListWaitingRoomRules lists all rules for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-list-waiting-room-rules
+func (api *API) ListWaitingRoomRules(ctx context.Context, zoneID string, waitingRoomID string) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomRulesResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+ }
+ return r.Result, nil
+}
+
+// CreateWaitingRoomRule creates a new rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room-rule
+func (api *API) CreateWaitingRoomRule(ctx context.Context, zoneID string, waitingRoomID string, rule WaitingRoomRule) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rule)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomRulesResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+ }
+ return r.Result, nil
+}
+
+// ReplaceWaitingRoomRules replaces all rules for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-replace-waiting-room-rules
+func (api *API) ReplaceWaitingRoomRules(ctx context.Context, zoneID string, waitingRoomID string, rules []WaitingRoomRule) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules", zoneID, waitingRoomID)
+ res, err := api.makeRequestContext(ctx, http.MethodPut, uri, rules)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomRulesResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+ }
+ return r.Result, nil
+}
+
+// UpdateWaitingRoomRule updates a rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-patch-waiting-room-rule
+func (api *API) UpdateWaitingRoomRule(ctx context.Context, zoneID string, waitingRoomID string, rule WaitingRoomRule) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", zoneID, waitingRoomID, rule.ID)
+ res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, rule)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomRulesResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+ }
+ return r.Result, nil
+}
+
+// DeleteWaitingRoomRule deletes a rule for a Waiting Room.
+//
+// API reference: https://api.cloudflare.com/#waiting-room-delete-waiting-room-rule
+func (api *API) DeleteWaitingRoomRule(ctx context.Context, zoneID string, waitingRoomID string, ruleId string) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", zoneID, waitingRoomID, ruleId)
+ res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+ if err != nil {
+ return nil, err
+ }
+ var r WaitingRoomRulesResponse
+ err = json.Unmarshal(res, &r)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
+ }
+ return r.Result, nil
+}

original_message: add waitingroom rules


message: add waitingroom rule tests

change_type: ADD

diff:
+```release-note:enhancement
+waiting room: add support for waiting room rules
+```
\ No newline at end of file

diff:
@@ -455,8 +455,8 @@ func (api *API) UpdateWaitingRoomRule(ctx context.Context, zoneID string, waitin
// DeleteWaitingRoomRule deletes a rule for a Waiting Room.
//
// API reference: https://api.cloudflare.com/#waiting-room-delete-waiting-room-rule
-func (api *API) DeleteWaitingRoomRule(ctx context.Context, zoneID string, waitingRoomID string, ruleId string) ([]WaitingRoomRule, error) {
- uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", zoneID, waitingRoomID, ruleId)
+func (api *API) DeleteWaitingRoomRule(ctx context.Context, zoneID string, waitingRoomID string, ruleID string) ([]WaitingRoomRule, error) {
+ uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s/rules/%s", zoneID, waitingRoomID, ruleID)
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return nil, err

diff:
@@ -13,6 +13,7 @@ import (
var waitingRoomID = "699d98642c564d2e855e9661899b7252"
var waitingRoomEventID = "25756b2dfe6e378a06b033b670413757"
+var waitingRoomRuleID = "25756b2dfe6e378a06b033b670413757"
var testTimestampWaitingRoom = time.Now().UTC()
var testTimestampWaitingRoomEvent = time.Now().UTC()
var testTimestampWaitingRoomEventPrequeue = time.Now().UTC()
@@ -78,6 +79,18 @@ var waitingRoomStatusJSON = fmt.Sprintf(`
}
`, waitingRoomEventID)
+var waitingRoomRuleJSON = fmt.Sprintf(`
+{
+ "id": "%s",
+ "version": "1",
+ "description": "bypass ip",
+ "action": "bypass_waiting_room",
+ "expression": "ip.src in {1.2.3.4 5.6.7.8}",
+ "enabled": true,
+ "last_updated": "%s"
+}
+`, waitingRoomRuleID, testTimestampWaitingRoom.Format(time.RFC3339Nano))
+
var waitingRoomPagePreviewJSON = `
{
"preview_url": "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb"
@@ -137,6 +150,16 @@ var waitingRoomPagePreview = WaitingRoomPagePreviewURL{
PreviewURL: "http://waitingrooms.dev/preview/35af8c12-6d68-4608-babb-b53435a5ddfb",
}
+var waitingRoomRule = WaitingRoomRule{
+ ID: waitingRoomRuleID,
+ Version: "1",
+ Action: "bypass_waiting_room",
+ Expression: "ip.src in {1.2.3.4 5.6.7.8}",
+ Description: "bypass ip",
+ Enabled: true,
+ LastUpdated: &testTimestampWaitingRoom,
+}
+
func TestListWaitingRooms(t *testing.T) {
setup()
defer teardown()
@@ -617,3 +640,136 @@ func TestDeleteWaitingRoomEvent(t *testing.T) {
err := client.DeleteWaitingRoomEvent(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", "25756b2dfe6e378a06b033b670413757")
assert.NoError(t, err)
}
+
+func TestListWaitingRoomRules(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": [
+ %s
+ ]
+ }
+ `, waitingRoomRuleJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+ want := []WaitingRoomRule{waitingRoomRule}
+
+ actual, err := client.ListWaitingRoomRules(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252")
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestCreateWaitingRoomRule(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": [
+ %s
+ ]
+ }
+ `, waitingRoomRuleJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+ want := []WaitingRoomRule{waitingRoomRule}
+
+ actual, err := client.CreateWaitingRoomRule(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomRule)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestUpdateWaitingRoomRule(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": [
+ %s
+ ]
+ }
+ `, waitingRoomRuleJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules/"+waitingRoomRuleID, handler)
+ want := []WaitingRoomRule{waitingRoomRule}
+
+ actual, err := client.UpdateWaitingRoomRule(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomRule)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestDeleteWaitingRoomRule(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprint(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": []
+ }
+ `)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules/"+waitingRoomRuleID, handler)
+ want := []WaitingRoomRule{}
+
+ actual, err := client.DeleteWaitingRoomRule(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", waitingRoomRuleID)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}
+
+func TestReplaceWaitingRoomRules(t *testing.T) {
+ setup()
+ defer teardown()
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+ w.Header().Set("content-type", "application/json")
+ fmt.Fprintf(w, `{
+ "success": true,
+ "errors": [],
+ "messages": [],
+ "result": [
+ %s
+ ]
+ }
+ `, waitingRoomRuleJSON)
+ }
+
+ mux.HandleFunc("/zones/"+testZoneID+"/waiting_rooms/699d98642c564d2e855e9661899b7252/rules", handler)
+ want := []WaitingRoomRule{waitingRoomRule}
+
+ actual, err := client.ReplaceWaitingRoomRules(context.Background(), testZoneID, "699d98642c564d2e855e9661899b7252", want)
+ if assert.NoError(t, err) {
+ assert.Equal(t, want, actual)
+ }
+}

original_message: add waitingroom rule tests


message:
Allow reading block-size of Rasters
1. implemented GetBlockSize
2. added feature flag "gdal_2_2" to enable recent GDAL
functions.
3. implemented GetActualBlockSize (behind "gdal_2_2" flag)

diff:
@@ -12,6 +12,7 @@ documentation = "https://georust.github.io/gdal/"
[features]
bindgen = ["gdal-sys/bindgen"]
+gdal_2_2 = ["gdal-sys/min_gdal_version_2_2"]
[dependencies]
failure = "0.1"

diff:
@@ -150,6 +150,35 @@ impl <'a> RasterBand<'a> {
}
None
}
+
+ pub fn block_size(&self) -> (usize, usize) {
+ let mut block_size_x = 0;
+ let mut block_size_y = 0;
+ unsafe { gdal_sys::GDALGetBlockSize(
+ self.c_rasterband,
+ &mut block_size_x,
+ &mut block_size_y,
+ )}
+ (block_size_x as usize, block_size_y as usize)
+ }
+
+ #[cfg(feature = "gdal_2_2")]
+ pub fn actual_block_size(&self, offset: (isize, isize))
+ -> Result<(usize, usize)>{
+ let mut block_size_x = 0;
+ let mut block_size_y = 0;
+ let rv = unsafe { gdal_sys::GDALGetActualBlockSize(
+ self.c_rasterband,
+ offset.0 as libc::c_int,
+ offset.1 as libc::c_int,
+ &mut block_size_x,
+ &mut block_size_y,
+ )};
+ if rv != CPLErr::CE_None {
+ Err(_last_cpl_err(rv))?;
+ }
+ Ok((block_size_x as usize, block_size_y as usize))
+ }
}
impl<'a> MajorObject for RasterBand<'a> {

repo: georust/gdal


message: Allow creating vector layer with geometry info

diff:
@@ -8,7 +8,7 @@ use gdal_major_object::MajorObject;
use metadata::Metadata;
use gdal_sys::{self, GDALMajorObjectH, OGRDataSourceH, OGRLayerH, OGRwkbGeometryType};
use utils::_last_null_pointer_err;
-
+use spatial_ref::SpatialRef;
use errors::*;
@@ -98,6 +98,34 @@ impl Dataset {
self._child_layer(c_layer);
Ok(self.layers.last_mut().unwrap()) // TODO: is this safe?
}
+
+ /// Create a new layer with name, spatial ref. and type.
+ pub fn create_layer_ext(
+ &mut self,
+ name: &str,
+ srs: Option<&SpatialRef>,
+ ty: OGRwkbGeometryType::Type,
+ ) -> Result<&mut Layer> {
+ let c_name = CString::new(name)?;
+ let c_srs = match srs {
+ Some(srs) => srs.to_c_hsrs(),
+ None => null_mut(),
+ };
+
+ let c_layer = unsafe { gdal_sys::OGR_DS_CreateLayer(
+ self.c_dataset,
+ c_name.as_ptr(),
+ c_srs,
+ ty,
+ null_mut(),
+ ) };
+ if c_layer.is_null() {
+ Err(_last_null_pointer_err("OGR_DS_CreateLayer"))?;
+ };
+ self._child_layer(c_layer);
+ Ok(self.layers.last_mut().unwrap()) // TODO: is this safe?
+
+ }
}


message:
Added support for getting dimensions of a RasterBand
+ added `x_size()`, `y_size()`, and `size()` to `RasterBand`
+ added tests

diff:
@@ -26,8 +26,6 @@ impl <'a> RasterBand<'a> {
}
/// Get block size from a 'Dataset'.
- /// # Arguments
- /// * band_index - the band_index
pub fn block_size(&self) -> (usize, usize) {
let mut size_x = 0;
let mut size_y = 0;
@@ -42,6 +40,31 @@ impl <'a> RasterBand<'a> {
(size_x as usize, size_y as usize)
}
+ /// Get x-size of the band
+ pub fn x_size(&self) -> usize {
+ let out;
+ unsafe {
+ out = gdal_sys::GDALGetRasterBandXSize(self.c_rasterband);
+ }
+ out as usize
+ }
+
+ /// Get y-size of the band
+ pub fn y_size(&self) -> usize {
+ let out;
+ unsafe {
+ out = gdal_sys::GDALGetRasterBandYSize(self.c_rasterband)
+ }
+ out as usize
+ }
+
+ /// Get dimensions of the band.
+ /// Note that this may not be the same as `size` on the
+ /// `owning_dataset` due to scale.
+ pub fn size(&self) -> (usize, usize) {
+ (self.x_size(), self.y_size())
+ }
+
/// Read a 'Buffer<T>' from a 'Dataset'. T implements 'GdalType'
/// # Arguments
/// * band_index - the band_index
@@ -252,17 +275,8 @@ impl <'a> RasterBand<'a> {
None
}
- pub fn block_size(&self) -> (usize, usize) {
- let mut block_size_x = 0;
- let mut block_size_y = 0;
- unsafe { gdal_sys::GDALGetBlockSize(
- self.c_rasterband,
- &mut block_size_x,
- &mut block_size_y,
- )}
- (block_size_x as usize, block_size_y as usize)
- }
-
+ /// Get actual block size (at the edges) when block size
+ /// does not divide band size.
#[cfg(feature = "gdal_2_2")]
pub fn actual_block_size(&self, offset: (isize, isize))
-> Result<(usize, usize)>{

diff:
@@ -365,3 +365,29 @@ fn test_get_offset() {
let offset = rasterband.offset();
assert_eq!(offset, Some(0.0));
}
+
+
+#[test]
+fn test_get_rasterband_size() {
+ let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap();
+ let rasterband = dataset.rasterband(1).unwrap();
+ let size = rasterband.size();
+ assert_eq!(size, (100, 50));
+}
+
+#[test]
+fn test_get_rasterband_block_size() {
+ let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap();
+ let rasterband = dataset.rasterband(1).unwrap();
+ let size = rasterband.block_size();
+ assert_eq!(size, (100, 1));
+}
+
+#[test]
+#[cfg(feature = "gdal_2_2")]
+fn test_get_rasterband_actual_block_size() {
+ let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap();
+ let rasterband = dataset.rasterband(1).unwrap();
+ let size = rasterband.actual_block_size((0,40));
+ assert_eq!(size.unwrap(), (100, 1));
+}


message:
Ability to read into preallocated slice for rasterband
+ add `read_into_slice`
+ make `read_as` and `read_as_array` to use it
+ minor doc fixes
+ add test for `read_into_slice`

diff:
@@ -64,20 +64,23 @@ impl<'a> RasterBand<'a> {
(self.x_size(), self.y_size())
}
- /// Read a 'Buffer<T>' from a 'Dataset'. T implements 'GdalType'
+ /// Read data from this band into a slice. T implements 'GdalType'
+ ///
/// # Arguments
- /// * band_index - the band_index
/// * window - the window position from top left
/// * window_size - the window size (GDAL will interpolate data if window_size != buffer_size)
- /// * buffer_size - the desired size of the 'Buffer'
- pub fn read_as<T: Copy + GdalType>(
+ /// * size - the desired size to read
+ /// * buffer - a slice to hold the data (length must equal product of size parameter)
+ pub fn read_into_slice<T: Copy + GdalType>(
&self,
window: (isize, isize),
window_size: (usize, usize),
size: (usize, usize),
- ) -> Result<Buffer<T>> {
+ buffer: &mut [T],
+ ) -> Result<()> {
let pixels = (size.0 * size.1) as usize;
- let mut data: Vec<T> = Vec::with_capacity(pixels);
+ assert_eq!(buffer.len(), pixels);
+
//let no_data:
let rv = unsafe {
gdal_sys::GDALRasterIO(
@@ -87,7 +90,7 @@ impl<'a> RasterBand<'a> {
window.1 as c_int,
window_size.0 as c_int,
window_size.1 as c_int,
- data.as_mut_ptr() as GDALRasterBandH,
+ buffer.as_mut_ptr() as GDALRasterBandH,
size.0 as c_int,
size.1 as c_int,
T::gdal_type(),
@@ -99,15 +102,41 @@ impl<'a> RasterBand<'a> {
return Err(_last_cpl_err(rv).into());
}
+ Ok(())
+ }
+
+ /// Read a 'Buffer<T>' from this band. T implements 'GdalType'
+ ///
+ /// # Arguments
+ /// * window - the window position from top left
+ /// * window_size - the window size (GDAL will interpolate data if window_size != buffer_size)
+ /// * buffer_size - the desired size of the 'Buffer'
+ pub fn read_as<T: Copy + GdalType>(
+ &self,
+ window: (isize, isize),
+ window_size: (usize, usize),
+ size: (usize, usize),
+ ) -> Result<Buffer<T>> {
+ let pixels = (size.0 * size.1) as usize;
+
+ let mut data: Vec<T> = Vec::with_capacity(pixels);
+
+ // Safety: the read_into_slice line below writes
+ // exactly pixel elements into the slice, before we
+ // read from this slice. This paradigm is suggested
+ // in the rust std docs
+ // (https://doc.rust-lang.org/std/vec/struct.Vec.html#examples-18)
unsafe {
data.set_len(pixels);
};
+ self.read_into_slice(window, window_size, size, &mut data)?;
Ok(Buffer { size, data })
}
#[cfg(feature = "ndarray")]
- /// Read a 'Array2<T>' from a 'Dataset'. T implements 'GdalType'.
+ /// Read a 'Array2<T>' from this band. T implements 'GdalType'.
+ ///
/// # Arguments
/// * window - the window position from top left
/// * window_size - the window size (GDAL will interpolate data if window_size != array_size)
@@ -120,38 +149,13 @@ impl<'a> RasterBand<'a> {
window_size: (usize, usize),
array_size: (usize, usize),
) -> Result<Array2<T>> {
- let pixels = (array_size.0 * array_size.1) as usize;
- let mut data: Vec<T> = Vec::with_capacity(pixels);
-
- let values = unsafe {
- gdal_sys::GDALRasterIO(
- self.c_rasterband,
- GDALRWFlag::GF_Read,
- window.0 as c_int,
- window.1 as c_int,
- window_size.0 as c_int,
- window_size.1 as c_int,
- data.as_mut_ptr() as GDALRasterBandH,
- array_size.0 as c_int,
- array_size.1 as c_int,
- T::gdal_type(),
- 0,
- 0,
- )
- };
- if values != CPLErr::CE_None {
- return Err(_last_cpl_err(values).into());
- }
-
- unsafe {
- data.set_len(pixels);
- };
+ let data = self.read_as::<T>(window, window_size, array_size)?;
// Matrix shape is (rows, cols) and raster shape is (cols in x-axis, rows in y-axis)
- Array2::from_shape_vec((array_size.1, array_size.0), data).map_err(Into::into)
+ Ok( Array2::from_shape_vec((array_size.1, array_size.0), data.data)? )
}
- /// Read a full 'Dataset' as 'Buffer<T>'.
+ /// Read the full band as a 'Buffer<T>'.
/// # Arguments
/// * band_index - the band_index
pub fn read_band_as<T: Copy + GdalType>(&self) -> Result<Buffer<T>> {


message:
Add documentation
+ add docs for `Dataset` and `RasterBand`.
+ fix `cfg()` for GDAL OpenEx flags.

diff:
@@ -16,9 +16,23 @@ use std::convert::TryInto;
use bitflags::bitflags;
+/// A 2-D affine transform mapping pixel coordiates to world
+/// coordinates. See [GDALGetGeoTransform] for more details.
+///
+/// [GDALGetGeoTransform]: https://gdal.org/api/gdaldataset_cpp.html#classGDALDataset_1a5101119705f5fa2bc1344ab26f66fd1d
pub type GeoTransform = [c_double; 6];
static START: Once = Once::new();
+/// Wrapper around a [`GDALDataset`][GDALDataset] object.
+///
+/// Represents both a [vector dataset][vector-data-model]
+/// containing a collection of layers; and a [raster
+/// dataset][raster-data-model] containing a collection of
+/// rasterbands.
+///
+/// [vector-data-model]: https://gdal.org/user/vector_data_model.html
+/// [raster-data-model]: https://gdal.org/user/raster_data_model.html
+/// [GDALDataset]: https://gdal.org/api/gdaldataset_cpp.html#_CPPv411GDALDataset
#[derive(Debug)]
pub struct Dataset {
c_dataset: GDALDatasetH,
@@ -32,44 +46,54 @@ pub fn _register_drivers() {
}
}
-// GDal extended open flags, skipped by bindgen
-//
-// Note that the `GDAL_OF_SHARED` option is removed from the set
-// of allowed option because it subverts the [`Send`] implementation
-// that allow passing the dataset the another thread.
-// See https://github.com/georust/gdal/issues/154.
+// These are skipped by bindgen and manually updated.
#[cfg(major_ge_2)]
bitflags! {
+ /// GDal extended open flags used by [`Dataset::open_ex`].
+ ///
+ /// Used in the `nOpenFlags` argument to [`GDALOpenEx`].
+ ///
+ /// Note that the `GDAL_OF_SHARED` option is removed
+ /// from the set of allowed option because it subverts
+ /// the [`Send`] implementation that allow passing the
+ /// dataset the another thread. See
+ /// https://github.com/georust/gdal/issues/154.
+ ///
+ /// [`GDALOpenEx`]: https://gdal.org/doxygen/gdal_8h.html#a9cb8585d0b3c16726b08e25bcc94274a
pub struct GdalOpenFlags: c_uint {
+ /// Open in read-only mode (default).
const GDAL_OF_READONLY = 0x00;
- // Open in update mode.
+ /// Open in update mode.
const GDAL_OF_UPDATE = 0x01;
- // Allow raster and vector drivers to be used.
+ /// Allow raster and vector drivers to be used.
const GDAL_OF_ALL = 0x00;
- // Allow raster drivers to be used.
+ /// Allow raster drivers to be used.
const GDAL_OF_RASTER = 0x02;
- // Allow vector drivers to be used.
+ /// Allow vector drivers to be used.
const GDAL_OF_VECTOR = 0x04;
- // Allow gnm drivers to be used.
- #[cfg(all(major_ge_2,minor_ge_1))]
+ /// Allow gnm drivers to be used.
+ #[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_GNM = 0x08;
- // Allow multidimensional raster drivers to be used.
+ /// Allow multidimensional raster drivers to be used.
#[cfg(all(major_ge_3,minor_ge_1))]
const GDAL_OF_MULTIDIM_RASTER = 0x10;
- // Emit error message in case of failed open.
+ /// Emit error message in case of failed open.
const GDAL_OF_VERBOSE_ERROR = 0x40;
- // Open as internal dataset. Such dataset isn't registered in the global list
- // of opened dataset. Cannot be used with GDAL_OF_SHARED.
+ /// Open as internal dataset. Such dataset isn't
+ /// registered in the global list of opened dataset.
+ /// Cannot be used with GDAL_OF_SHARED.
const GDAL_OF_INTERNAL = 0x80;
- // Let GDAL decide if a array-based or hashset-based storage strategy for
- // cached blocks must be used.
- // GDAL_OF_DEFAULT_BLOCK_ACCESS, GDAL_OF_ARRAY_BLOCK_ACCESS and
- // GDAL_OF_HASHSET_BLOCK_ACCESS are mutually exclusive.
- #[cfg(all(major_ge_2,minor_ge_1))]
+
+ /// Default strategy for cached blocks.
+ #[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_DEFAULT_BLOCK_ACCESS = 0;
- #[cfg(all(major_ge_2,minor_ge_1))]
+
+ /// Array based strategy for cached blocks.
+ #[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_ARRAY_BLOCK_ACCESS = 0x100;
- #[cfg(all(major_ge_2,minor_ge_1))]
+
+ /// Hashset based strategy for cached blocks.
+ #[cfg(any( all(major_ge_2,minor_ge_1), major_ge_3 ))]
const GDAL_OF_HASHSET_BLOCK_ACCESS = 0x200;
}
}
@@ -115,10 +139,14 @@ impl Dataset {
self.c_dataset
}
+ /// Open a dataset at the given `path` with default
+ /// options.
pub fn open(path: &Path) -> Result<Dataset> {
Self::open_ex(path, DatasetOptions::default())
}
+ /// Open a dataset with extended options. See
+ /// [GDALOpenEx].
pub fn open_ex(path: &Path, options: DatasetOptions) -> Result<Dataset> {
_register_drivers();
let filename = path.to_string_lossy();
@@ -217,11 +245,13 @@ impl Dataset {
Dataset { c_dataset }
}
+ /// Fetch the projection definition string for this dataset.
pub fn projection(&self) -> String {
let rv = unsafe { gdal_sys::GDALGetProjectionRef(self.c_dataset) };
_string(rv)
}
+ /// Set the projection reference string for this dataset.
pub fn set_projection(&self, projection: &str) -> Result<()> {
let c_projection = CString::new(projection)?;
unsafe { gdal_sys::GDALSetProjection(self.c_dataset, c_projection.as_ptr()) };
@@ -229,11 +259,13 @@ impl Dataset {
}
#[cfg(major_ge_3)]
+ /// Get the spatial reference system for this dataset.
pub fn spatial_ref(&self) -> Result<SpatialRef> {
unsafe { SpatialRef::from_c_obj(gdal_sys::GDALGetSpatialRef(self.c_dataset)) }
}
#[cfg(major_ge_3)]
+ /// Set the spatial reference system for this dataset.
pub fn set_spatial_ref(&self, spatial_ref: &SpatialRef) -> Result<()> {
let rv = unsafe { gdal_sys::GDALSetSpatialRef(self.c_dataset, spatial_ref.to_c_hsrs()) };
if rv != CPLErr::CE_None {
@@ -261,6 +293,7 @@ impl Dataset {
Ok(unsafe { Dataset::from_c_dataset(c_dataset) })
}
+ /// Fetch the driver to which this dataset relates.
pub fn driver(&self) -> Driver {
unsafe {
let c_driver = gdal_sys::GDALGetDatasetDriver(self.c_dataset);
@@ -268,6 +301,10 @@ impl Dataset {
}
}
+ /// Fetch a band object for a dataset.
+ ///
+ /// Applies to raster datasets, and fetches the
+ /// rasterband at the given _1-based_ index.
pub fn rasterband(&self, band_index: isize) -> Result<RasterBand> {
unsafe {
let c_band = gdal_sys::GDALGetRasterBand(self.c_dataset, band_index as c_int);
@@ -282,10 +319,15 @@ impl Dataset {
unsafe { Layer::from_c_layer(self, c_layer) }
}
+ /// Get the number of layers in this dataset.
pub fn layer_count(&self) -> isize {
(unsafe { gdal_sys::OGR_DS_GetLayerCount(self.c_dataset) }) as isize
}
+ /// Fetch a layer by index.
+ ///
+ /// Applies to vector datasets, and fetches by the given
+ /// _0-based_ index.
pub fn layer(&mut self, idx: isize) -> Result<Layer> {
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
if c_layer.is_null() {
@@ -294,6 +336,7 @@ impl Dataset {
Ok(self.child_layer(c_layer))
}
+ /// Fetch a layer by name.
pub fn layer_by_name(&mut self, name: &str) -> Result<Layer> {
let c_name = CString::new(name)?;
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayerByName(self.c_dataset(), c_name.as_ptr()) };
@@ -303,14 +346,17 @@ impl Dataset {
Ok(self.child_layer(c_layer))
}
+ /// Returns an iterator over the layers of the dataset.
pub fn layers(&self) -> LayerIterator {
LayerIterator::with_dataset(self)
}
+ /// Fetch the number of raster bands on this dataset.
pub fn raster_count(&self) -> isize {
(unsafe { gdal_sys::GDALGetRasterCount(self.c_dataset) }) as isize
}
+ /// Returns the raster dimensions: (width, height).
pub fn raster_size(&self) -> (usize, usize) {
let size_x = unsafe { gdal_sys::GDALGetRasterXSize(self.c_dataset) } as usize;
let size_y = unsafe { gdal_sys::GDALGetRasterYSize(self.c_dataset) } as usize;

diff:
@@ -202,7 +202,7 @@ impl<'a> RasterBand<'a> {
Array2::from_shape_vec((size.1, size.0), data).map_err(Into::into)
}
- // Write a 'Buffer<T>' into a 'Dataset'.
+ /// Write a 'Buffer<T>' into a 'Dataset'.
/// # Arguments
/// * band_index - the band_index
/// * window - the window position from top left
@@ -236,10 +236,12 @@ impl<'a> RasterBand<'a> {
Ok(())
}
+ /// Returns the pixel datatype of this band.
pub fn band_type(&self) -> GDALDataType::Type {
unsafe { gdal_sys::GDALGetRasterDataType(self.c_rasterband) }
}
+ /// Returns the no data value of this band.
pub fn no_data_value(&self) -> Option<f64> {
let mut pb_success = 1;
let no_data =
@@ -250,6 +252,7 @@ impl<'a> RasterBand<'a> {
None
}
+ /// Set the no data value of this band.
pub fn set_no_data_value(&self, no_data: f64) -> Result<()> {
let rv = unsafe { gdal_sys::GDALSetRasterNoDataValue(self.c_rasterband, no_data) };
if rv != CPLErr::CE_None {
@@ -258,6 +261,7 @@ impl<'a> RasterBand<'a> {
Ok(())
}
+ /// Returns the scale of this band if set.
pub fn scale(&self) -> Option<f64> {
let mut pb_success = 1;
let scale = unsafe { gdal_sys::GDALGetRasterScale(self.c_rasterband, &mut pb_success) };
@@ -267,6 +271,7 @@ impl<'a> RasterBand<'a> {
None
}
+ /// Returns the offset of this band if set.
pub fn offset(&self) -> Option<f64> {
let mut pb_success = 1;
let offset = unsafe { gdal_sys::GDALGetRasterOffset(self.c_rasterband, &mut pb_success) };


message: Fix method self arguments

diff:
@@ -252,7 +252,7 @@ impl Dataset {
}
/// Set the projection reference string for this dataset.
- pub fn set_projection(&self, projection: &str) -> Result<()> {
+ pub fn set_projection(&mut self, projection: &str) -> Result<()> {
let c_projection = CString::new(projection)?;
unsafe { gdal_sys::GDALSetProjection(self.c_dataset, c_projection.as_ptr()) };
Ok(())
@@ -266,7 +266,7 @@ impl Dataset {
#[cfg(major_ge_3)]
/// Set the spatial reference system for this dataset.
- pub fn set_spatial_ref(&self, spatial_ref: &SpatialRef) -> Result<()> {
+ pub fn set_spatial_ref(&mut self, spatial_ref: &SpatialRef) -> Result<()> {
let rv = unsafe { gdal_sys::GDALSetSpatialRef(self.c_dataset, spatial_ref.to_c_hsrs()) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));
@@ -328,7 +328,7 @@ impl Dataset {
///
/// Applies to vector datasets, and fetches by the given
/// _0-based_ index.
- pub fn layer(&mut self, idx: isize) -> Result<Layer> {
+ pub fn layer(&self, idx: isize) -> Result<Layer> {
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
if c_layer.is_null() {
return Err(_last_null_pointer_err("OGR_DS_GetLayer"));
@@ -337,7 +337,7 @@ impl Dataset {
}
/// Fetch a layer by name.
- pub fn layer_by_name(&mut self, name: &str) -> Result<Layer> {
+ pub fn layer_by_name(&self, name: &str) -> Result<Layer> {
let c_name = CString::new(name)?;
let c_layer = unsafe { gdal_sys::OGR_DS_GetLayerByName(self.c_dataset(), c_name.as_ptr()) };
if c_layer.is_null() {
@@ -403,7 +403,7 @@ impl Dataset {
/// y-coordinate of the top-left corner pixel
/// column rotation (typically zero)
/// height of a pixel (y-resolution, typically negative)
- pub fn set_geo_transform(&self, transformation: &GeoTransform) -> Result<()> {
+ pub fn set_geo_transform(&mut self, transformation: &GeoTransform) -> Result<()> {
assert_eq!(transformation.len(), 6);
let rv = unsafe {
gdal_sys::GDALSetGeoTransform(self.c_dataset, transformation.as_ptr() as *mut f64)

diff:
@@ -208,7 +208,7 @@ impl<'a> RasterBand<'a> {
/// * window - the window position from top left
/// * window_size - the window size (GDAL will interpolate data if window_size != Buffer.size)
pub fn write<T: GdalType + Copy>(
- &self,
+ &mut self,
window: (isize, isize),
window_size: (usize, usize),
buffer: &Buffer<T>,
@@ -253,7 +253,7 @@ impl<'a> RasterBand<'a> {
}
/// Set the no data value of this band.
- pub fn set_no_data_value(&self, no_data: f64) -> Result<()> {
+ pub fn set_no_data_value(&mut self, no_data: f64) -> Result<()> {
let rv = unsafe { gdal_sys::GDALSetRasterNoDataValue(self.c_rasterband, no_data) };
if rv != CPLErr::CE_None {
return Err(_last_cpl_err(rv));

diff:
@@ -87,11 +87,11 @@ impl<'a> Layer<'a> {
FeatureIterator::_with_layer(self)
}
- pub fn set_spatial_filter(&self, geometry: &Geometry) {
+ pub fn set_spatial_filter(&mut self, geometry: &Geometry) {
unsafe { gdal_sys::OGR_L_SetSpatialFilter(self.c_layer, geometry.c_geometry()) };
}
- pub fn clear_spatial_filter(&self) {
+ pub fn clear_spatial_filter(&mut self) {
unsafe { gdal_sys::OGR_L_SetSpatialFilter(self.c_layer, null_mut()) };
}

original_message: Fix method self arguments


diff:
@@ -7,7 +7,7 @@ use std::path::Path;
fn run() -> Result<()> {
let dataset_a = Dataset::open(Path::new("fixtures/roads.geojson"))?;
- let layer_a = dataset_a.layer(0)?;
+ let mut layer_a = dataset_a.layer(0)?;
let fields_defn = layer_a
.defn()
.fields()

diff:
//! use std::path::Path;
//! use gdal::Dataset;
//!
-//! let mut dataset = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap();
-//! let layer = dataset.layer(0).unwrap();
+//! let dataset = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap();
+//! let mut layer = dataset.layer(0).unwrap();
//! for feature in layer.features() {
//! let highway_field = feature.field("highway").unwrap().unwrap();
//! let geometry = feature.geometry();

diff:
@@ -19,8 +19,8 @@ use crate::errors::*;
/// use std::path::Path;
/// use gdal::Dataset;
///
-/// let mut dataset = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap();
-/// let layer = dataset.layer(0).unwrap();
+/// let dataset = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap();
+/// let mut layer = dataset.layer(0).unwrap();
/// for feature in layer.features() {
/// // do something with each feature
/// }
@@ -83,7 +83,7 @@ impl<'a> Layer<'a> {
}
/// Iterate over all features in this layer.
- pub fn features(&self) -> FeatureIterator {
+ pub fn features(&mut self) -> FeatureIterator {
FeatureIterator::_with_layer(self)
}
@@ -244,7 +244,9 @@ impl<'a> Layer<'a> {
}
pub struct FeatureIterator<'a> {
- layer: &'a Layer<'a>,
+ defn: &'a Defn,
+ c_layer: OGRLayerH,
+ size_hint: Option<usize>,
}
impl<'a> Iterator for FeatureIterator<'a> {
@@ -252,21 +254,16 @@ impl<'a> Iterator for FeatureIterator<'a> {
#[inline]
fn next(&mut self) -> Option<Feature<'a>> {
- let c_feature = unsafe { gdal_sys::OGR_L_GetNextFeature(self.layer.c_layer) };
+ let c_feature = unsafe { gdal_sys::OGR_L_GetNextFeature(self.c_layer) };
if c_feature.is_null() {
None
} else {
- Some(unsafe { Feature::from_c_feature(self.layer.defn(), c_feature) })
+ Some(unsafe { Feature::from_c_feature(self.defn, c_feature) })
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
- match self
- .layer
- .try_feature_count()
- .map(|s| s.try_into().ok())
- .flatten()
- {
+ match self.size_hint {
Some(size) => (size, Some(size)),
None => (0, None),
}
@@ -275,7 +272,12 @@ impl<'a> Iterator for FeatureIterator<'a> {
impl<'a> FeatureIterator<'a> {
pub fn _with_layer(layer: &'a Layer) -> FeatureIterator<'a> {
- FeatureIterator { layer }
+ let defn = layer.defn();
+ let size_hint = layer
+ .try_feature_count()
+ .map(|s| s.try_into().ok())
+ .flatten();
+ FeatureIterator { c_layer: layer.c_layer, size_hint, defn }
}
}

diff:
@@ -85,7 +85,7 @@ fn with_features<F>(name: &str, f: F)
where
F: Fn(FeatureIterator),
{
- with_layer(name, |layer| f(layer.features()));
+ with_layer(name, |mut layer| f(layer.features()));
}
fn with_feature<F>(name: &str, fid: u64, f: F)
@@ -205,7 +205,7 @@ mod tests {
#[test]
fn test_field_in_layer() {
- ds_with_layer("three_layer_ds.s3db", "layer_0", |layer| {
+ ds_with_layer("three_layer_ds.s3db", "layer_0", |mut layer| {
let feature = layer.features().next().unwrap();
assert_eq!(feature.field("id").unwrap(), None);
});
@@ -481,7 +481,7 @@ mod tests {
let ds = Dataset::open(fixture!("output.geojson")).unwrap();
fs::remove_file(fixture!("output.geojson")).unwrap();
- let layer = ds.layer(0).unwrap();
+ let mut layer = ds.layer(0).unwrap();
let ft = layer.features().next().unwrap();
assert_eq!(ft.geometry().wkt().unwrap(), "POINT (1 2)");
assert_eq!(
@@ -491,4 +491,20 @@ mod tests {
assert_eq!(ft.field("Value").unwrap().unwrap().into_real(), Some(45.78));
assert_eq!(ft.field("Int_value").unwrap().unwrap().into_int(), Some(1));
}
+
+ // A compilation test that should fail.
+ //
+ // It tries to iterate over a layer's features, while
+ // also trying to read the layer's definition. The
+ // features iterator borrows layer as a mutable ref as
+ // it increments the internal state of the layer.
+ //
+ // fn test_features_mut_lifetime_enforce() {
+ // with_layer("roads.geojson", |mut layer| {
+ // for _ in layer.features() {
+ // let _ = layer.defn();
+ // }
+ // });
+ // }
+
}

original_message:
Make features iterator take mut ref

Refer: https://github.com/georust/gdal/issues/159


message: Make features iterator reset next feature pointer

diff:
@@ -84,6 +84,7 @@ impl<'a> Layer<'a> {
/// Iterate over all features in this layer.
pub fn features(&mut self) -> FeatureIterator {
+ self.reset_feature_reading();
FeatureIterator::_with_layer(self)
}
@@ -241,6 +242,12 @@ impl<'a> Layer<'a> {
}
SpatialRef::from_c_obj(c_obj)
}
+
+ fn reset_feature_reading(&mut self) {
+ unsafe {
+ gdal_sys::OGR_L_ResetReading(self.c_layer);
+ }
+ }
}
pub struct FeatureIterator<'a> {

diff:
@@ -492,6 +492,17 @@ mod tests {
assert_eq!(ft.field("Int_value").unwrap().unwrap().into_int(), Some(1));
}
+ #[test]
+ fn test_features_reset() {
+ with_layer("roads.geojson", |mut layer| {
+ assert_eq!(
+ layer.features().count(),
+ layer.features().count(),
+ );
+ });
+
+ }
+
// A compilation test that should fail.
//
// It tries to iterate over a layer's features, while


message: Fix compilation errors on all-features

diff:
@@ -10,8 +10,8 @@ fn run() -> Result<()> {
println!("gdal crate was build with datetime support");
- let mut dataset_a = Dataset::open(Path::new("fixtures/points_with_datetime.json"))?;
- let layer_a = dataset_a.layer(0)?;
+ let dataset_a = Dataset::open(Path::new("fixtures/points_with_datetime.json"))?;
+ let mut layer_a = dataset_a.layer(0)?;
// Create a new dataset:
let path = std::env::temp_dir().join("later.geojson");

original_message: Fix compilation errors on all-features


diff:
@@ -31,6 +31,7 @@ semver = "0.11"
[dev-dependencies]
tempfile = "3.1"
+trybuild = "1.0.39"
[workspace]
members = ["gdal-sys"]

diff:
@@ -69,10 +69,6 @@ impl<'a> Layer<'a> {
/// Not all drivers support this efficiently; however, the call should always work if the
/// feature exists, as a fallback implementation just scans all the features in the layer
/// looking for the desired feature.
- ///
- /// **Important**: do not call `feature` while iterating over features using the
- /// [`features`](Layer::features) function. Even if you don't mutate anything, this can cause
- /// the the iterator to get interrupted. This is a limitation of the GDAL library.
pub fn feature(&self, fid: u64) -> Option<Feature> {
let c_feature = unsafe { gdal_sys::OGR_L_GetFeature(self.c_layer, fid as i64) };
if c_feature.is_null() {
@@ -82,16 +78,24 @@ impl<'a> Layer<'a> {
}
}
- /// Iterate over all features in this layer.
+ /// Returns iterator over the features in this layer.
+ ///
+ /// **Note.** This method resets the current index to
+ /// the beginning before iteration. It also borrows the
+ /// layer mutably, preventing any overlapping borrows.
pub fn features(&mut self) -> FeatureIterator {
self.reset_feature_reading();
FeatureIterator::_with_layer(self)
}
+ /// Set a spatial filter on this layer.
+ ///
+ /// Refer [OGR_L_SetSpatialFilter](https://gdal.org/doxygen/classOGRLayer.html#a75c06b4993f8eb76b569f37365cd19ab)
pub fn set_spatial_filter(&mut self, geometry: &Geometry) {
unsafe { gdal_sys::OGR_L_SetSpatialFilter(self.c_layer, geometry.c_geometry()) };
}
+ /// Clear spatial filters set on this layer.
pub fn clear_spatial_filter(&mut self) {
unsafe { gdal_sys::OGR_L_SetSpatialFilter(self.c_layer, null_mut()) };
}
@@ -235,6 +239,9 @@ impl<'a> Layer<'a> {
}
}
+ /// Fetch the spatial reference system for this layer.
+ ///
+ /// Refer [OGR_L_GetSpatialRef](https://gdal.org/doxygen/classOGRLayer.html#a75c06b4993f8eb76b569f37365cd19ab)
pub fn spatial_ref(&self) -> Result<SpatialRef> {
let c_obj = unsafe { gdal_sys::OGR_L_GetSpatialRef(self.c_layer) };
if c_obj.is_null() {

diff:
@@ -499,8 +499,12 @@ mod tests {
});
}
- // A compilation test that should fail.
- //
+ #[test]
+ fn test_features_aliasing_compile_fail() {
+ let t = trybuild::TestCases::new(); // A compilation test that should fail.
+ t.compile_fail("tests/ui/01-features-aliasing-errors.rs");
+ }
+
// It tries to iterate over a layer's features, while
// also trying to read the layer's definition. The
// features iterator borrows layer as a mutable ref as

diff:
+mod utils;
+
+use gdal::Dataset;
+
+fn main() {
+ let ds = Dataset::open(fixture!("roads.geojson")).unwrap();
+ let mut layer = ds.layer(0).unwrap();
+ for _ in layer.features() {
+ let _ = layer.defn();
+ }
+}

diff:
+error[E0502]: cannot borrow `layer` as immutable because it is also borrowed as mutable
+ --> $DIR/01-features-aliasing-errors.rs:9:17
+ |
+8 | for _ in layer.features() {
+ | ----------------
+ | |
+ | mutable borrow occurs here
+ | mutable borrow later used here
+9 | let _ = layer.defn();
+ | ^^^^^ immutable borrow occurs here

diff:
+#[macro_export]
+macro_rules! fixture {
+ ($name:expr) => {
+ std::path::Path::new(file!())
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .join("fixtures")
+ .as_path()
+ .join($name)
+ .as_path()
+ };
+}

original_message:
Add compile tests to ensure borrow semantics

+ add `trybuild`

+ add test to verify `Layer::features` mut-borrow. Refer
  https://github.com/georust/gdal/issues/159


message: Rename compile test folder more meaningfully

diff:
@@ -502,7 +502,7 @@ mod tests {
#[test]
fn test_features_aliasing_compile_fail() {
let t = trybuild::TestCases::new(); // A compilation test that should fail.
- t.compile_fail("tests/ui/01-features-aliasing-errors.rs");
+ t.compile_fail("tests/compile-tests/01-features-aliasing-errors.rs");
}
// It tries to iterate over a layer's features, while

change_type: RENAME


message: Support 2D in CoordTransform

diff:
@@ -2,7 +2,7 @@ use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string};
use gdal_sys::{self, CPLErr, OGRCoordinateTransformationH, OGRErr, OGRSpatialReferenceH};
use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
-use std::ptr;
+use std::ptr::{self, null_mut};
use std::str::FromStr;
use crate::errors::*;
@@ -42,7 +42,12 @@ impl CoordTransform {
nb_coords as c_int,
x.as_mut_ptr(),
y.as_mut_ptr(),
- z.as_mut_ptr(),
+ if z.is_empty() {
+ null_mut()
+ } else {
+ assert_eq!(nb_coords, z.len());
+ z.as_mut_ptr()
+ },
) == 1
};

diff:
@@ -81,7 +81,7 @@ fn transform_coordinates() {
let mut xs = [23.43, 23.50];
let mut ys = [37.58, 37.70];
transform
- .transform_coords(&mut xs, &mut ys, &mut [0.0, 0.0])
+ .transform_coords(&mut xs, &mut ys, &mut [])
.unwrap();
assert_almost_eq(xs[0], 5509543.1508097);
assert_almost_eq(ys[0], 1716062.1916192223);

original_message: Support 2D in CoordTransform


message: Fix failing error

diff:
@@ -169,7 +169,7 @@ fn failing_transformation() {
let mut x = [1000000.0];
let mut y = [1000000.0];
- let mut z = [0.0, 0.0];
+ let mut z = [0.0];
let trafo = CoordTransform::new(&wgs84, &webmercator).unwrap();
let r = trafo.transform_coords(&mut x, &mut y, &mut z);

original_message: Fix failing error


message: Add doc and message on assertion

diff:
@@ -33,9 +33,21 @@ impl CoordTransform {
})
}
+ /// Transform coordinates in place.
+ ///
+ /// # Arguments
+ /// * x - slice of x coordinates
+ /// * y - slice of y coordinates (must match x in length)
+ /// * z - slice of z coordinates, or an empty slice to ignore
pub fn transform_coords(&self, x: &mut [f64], y: &mut [f64], z: &mut [f64]) -> Result<()> {
let nb_coords = x.len();
- assert_eq!(nb_coords, y.len());
+ assert_eq!(
+ nb_coords,
+ y.len(),
+ "transform coordinate slices have different length: {} != {}",
+ nb_coords,
+ y.len()
+ );
let ret_val = unsafe {
gdal_sys::OCTTransform(
self.inner,
@@ -45,7 +57,13 @@ impl CoordTransform {
if z.is_empty() {
null_mut()
} else {
- assert_eq!(nb_coords, z.len());
+ assert_eq!(
+ nb_coords,
+ z.len(),
+ "transform coordinate slices have different length: {} != {}",
+ nb_coords,
+ z.len()
+ );
z.as_mut_ptr()
},
) == 1

original_message: Add doc and message on assertion


message: Fix clippy warning in test rasterband lifetime

diff:
@@ -631,10 +631,13 @@ fn test_fail_read_overviews() {
#[test]
fn test_rasterband_lifetime() {
let dataset: Dataset = Dataset::open(fixture!("tinymarble.tif")).unwrap();
+
+ let overview = {
let rasterband = dataset.rasterband(1).unwrap();
let overview = rasterband.overview(0).unwrap();
+ overview
+ };
- drop(rasterband);
assert!(overview.no_data_value().is_none());
}


message:
Fix crate build on docs.rs
Use dynamic code-gen to build-around non-availabiltiy of some packages.

diff:
use std::str::FromStr;
-#[cfg(docsrs)]
-pub fn gdal_version_info(_key: &str) -> String {
- "3020000".to_string()
-}
-
-#[cfg(not(docsrs))]
-pub fn gdal_version_info(key: &str) -> String {
- let c_key = std::ffi::CString::new(key.as_bytes()).unwrap();
-
- unsafe {
- let res_ptr = gdal_sys::GDALVersionInfo(c_key.as_ptr());
- let c_res = std::ffi::CStr::from_ptr(res_ptr);
- c_res.to_string_lossy().into_owned()
- }
+fn gdal_version_info() -> String {
+ gdal_sys::gdal_version_docs_rs_wrapper()
}
fn main() {
- let gdal_version_string = gdal_version_info("VERSION_NUM");
+ let gdal_version_string = gdal_version_info();
println!("GDAL version string: \"{}\"", gdal_version_string);
// this version string is the result of:

diff:
@@ -57,7 +57,31 @@ fn find_gdal_dll(lib_dir: &Path) -> io::Result<Option<String>> {
Ok(None)
}
+fn add_docs_rs_helper() {
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("docs_rs_helper.rs");
+ let docs_helper_code = if std::env::var_os("DOCS_RS").is_some() {
+ r#"
+ pub fn gdal_version_docs_rs_wrapper() -> String {
+ "3020000".to_string()
+ }"#
+ } else {
+ r#"
+ pub fn gdal_version_docs_rs_wrapper() -> String {
+ let key = "VERSION_NUM";
+ let c_key = std::ffi::CString::new(key.as_bytes()).unwrap();
+
+ unsafe {
+ let res_ptr = crate::GDALVersionInfo(c_key.as_ptr());
+ let c_res = std::ffi::CStr::from_ptr(res_ptr);
+ c_res.to_string_lossy().into_owned()
+ }
+ }"#
+ };
+ std::fs::write(&out_path, docs_helper_code.as_bytes()).unwrap();
+}
+
fn main() {
+ add_docs_rs_helper();
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
// Hardcode a prebuilt binding version while generating docs.
@@ -234,4 +258,5 @@ fn main() {
panic!("No GDAL version detected");
}
}
+
}

diff:
#![allow(clippy::upper_case_acronyms)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+include!(concat!(env!("OUT_DIR"), "/docs_rs_helper.rs"));


message:
Update documentation version
Use latest GDAL 3.5 pre-bindings for compiling for docs.

diff:
@@ -57,13 +57,15 @@ fn find_gdal_dll(lib_dir: &Path) -> io::Result<Option<String>> {
Ok(None)
}
-fn add_docs_rs_helper() {
+fn add_docs_rs_helper(version: Option<&str>) {
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("docs_rs_helper.rs");
- let docs_helper_code = if std::env::var_os("DOCS_RS").is_some() {
+ let docs_helper_code = if let Some(version) = version {
+ format!(
r#"
- pub fn gdal_version_docs_rs_wrapper() -> String {
- "3020000".to_string()
- }"#
+ pub fn gdal_version_docs_rs_wrapper() -> String {{
+ "{version}".to_string()
+ }}"#
+ )
} else {
r#"
pub fn gdal_version_docs_rs_wrapper() -> String {
@@ -76,18 +78,19 @@ fn add_docs_rs_helper() {
c_res.to_string_lossy().into_owned()
}
}"#
+ .to_string()
};
std::fs::write(&out_path, docs_helper_code.as_bytes()).unwrap();
}
fn main() {
- add_docs_rs_helper();
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
// Hardcode a prebuilt binding version while generating docs.
// Otherwise docs.rs will explode due to not actually having libgdal installed.
if std::env::var("DOCS_RS").is_ok() {
- let version = Version::parse("3.2.0").expect("invalid version for docs.rs");
+ let version = Version::parse("3.5.0").expect("invalid version for docs.rs");
+ add_docs_rs_helper(Some("3050000"));
println!(
"cargo:rustc-cfg=gdal_sys_{}_{}_{}",
version.major, version.minor, version.patch
@@ -107,6 +110,7 @@ fn main() {
return;
}
+ add_docs_rs_helper(None);
println!("cargo:rerun-if-env-changed=GDAL_STATIC");
println!("cargo:rerun-if-env-changed=GDAL_DYNAMIC");
println!("cargo:rerun-if-env-changed=GDAL_INCLUDE_DIR");


diff:
@@ -43,7 +43,7 @@ jobs:
rm -rf tensorflow_io
echo ${{ matrix.version }} | awk -F: '{print $1}' | xargs python -m pip install -U
echo ${{ matrix.version }} | awk -F: '{print $2}' | xargs python -m pip install --no-deps -U
- python -m pip install pytest-benchmark boto3
+ python -m pip install pytest-benchmark boto3 google-cloud-storage==1.32.0
python -m pip freeze
python -c 'import tensorflow as tf; print(tf.version.VERSION)'
python -c 'import tensorflow_io as tfio; print(tfio.version.VERSION)'
@@ -79,7 +79,7 @@ jobs:
rm -rf tensorflow_io
echo ${{ matrix.version }} | awk -F: '{print $1}' | xargs python -m pip install -U
echo ${{ matrix.version }} | awk -F: '{print $2}' | xargs python -m pip install --no-deps -U
- python -m pip install pytest-benchmark boto3
+ python -m pip install pytest-benchmark boto3 google-cloud-storage==1.32.0
python -m pip freeze
python -c 'import tensorflow as tf; print(tf.version.VERSION)'
python -c 'import tensorflow_io as tfio; print(tfio.version.VERSION)'

license: Apache License 2.0

repo: tensorflow/io

original_message: fix nightly build because of missing `google-cloud-storage` (#1238)


message: add tf-c-header rule

diff:
@@ -16,7 +16,7 @@ cc_library(
linkstatic = True,
deps = [
"@local_config_tf//:libtensorflow_framework",
- "@local_config_tf//:tf_header_lib",
+ "@local_config_tf//:tf_c_header_lib",
],
alwayslink = 1,
)

diff:
@@ -16,6 +16,8 @@ cc_library(
linkstatic = True,
deps = [
"//tensorflow_io/core/plugins:plugins_header",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/synchronization",
"@hadoop",
],
alwayslink = 1,

diff:
@@ -7,6 +7,14 @@ cc_library(
visibility = ["//visibility:public"],
)
+cc_library(
+ name = "tf_c_header_lib",
+ hdrs = [":tf_c_header_include"],
+ include_prefix = "tensorflow/c",
+ strip_include_prefix = "include_c",
+ visibility = ["//visibility:public"],
+)
+
cc_library(
name = "libtensorflow_framework",
srcs = [":libtensorflow_framework.so"],
@@ -15,4 +23,5 @@ cc_library(
)
%{TF_HEADER_GENRULE}
+%{TF_C_HEADER_GENRULE}
%{TF_SHARED_LIBRARY_GENRULE}

diff:
@@ -176,6 +176,12 @@ def _tf_pip_impl(repository_ctx):
"tf_header_include",
tf_pip_dir_rename_pair = ["tensorflow_core", "tensorflow"],
)
+ tf_c_header_rule = _symlink_genrule_for_dir(
+ repository_ctx,
+ tf_header_dir + "/tensorflow/c/",
+ "include_c",
+ "tf_c_header_include",
+ )
tf_shared_library_dir = repository_ctx.os.environ[_TF_SHARED_LIBRARY_DIR]
tf_shared_library_name = repository_ctx.os.environ[_TF_SHARED_LIBRARY_NAME]
@@ -192,6 +198,7 @@ def _tf_pip_impl(repository_ctx):
_tpl(repository_ctx, "BUILD", {
"%{TF_HEADER_GENRULE}": tf_header_rule,
+ "%{TF_C_HEADER_GENRULE}": tf_c_header_rule,
"%{TF_SHARED_LIBRARY_GENRULE}": tf_shared_library_rule,
})

original_message: add tf-c-header rule (#1244)


old_path: WORKSPACE

new_path: WORKSPACE

diff:
@@ -12,11 +12,14 @@ cuda_configure(name = "local_config_cuda")
http_archive(
name = "com_google_protobuf",
- sha256 = "cfcba2df10feec52a84208693937c17a4b5df7775e1635c1e3baffc487b24c9b",
- strip_prefix = "protobuf-3.9.2",
+ patch_cmds = [
+ """sed -i.bak 's/@six\\/\\/:six/\\/\\/external:six/g' BUILD""",
+ ],
+ sha256 = "9748c0d90e54ea09e5e75fb7fac16edce15d2028d4356f32211cfa3c0e956564",
+ strip_prefix = "protobuf-3.11.4",
urls = [
- "https://storage.googleapis.com/mirror.tensorflow.org/github.com/protocolbuffers/protobuf/archive/v3.9.2.zip",
- "https://github.com/protocolbuffers/protobuf/archive/v3.9.2.zip",
+ "https://storage.googleapis.com/mirror.tensorflow.org/github.com/protocolbuffers/protobuf/archive/v3.11.4.zip",
+ "https://github.com/protocolbuffers/protobuf/archive/v3.11.4.zip",
],
)

original_message: update `protobuf` version to `3.11.4` to match tensorflow-nightly (#1320)


diff:
@@ -24,9 +24,11 @@ import tensorflow as tf
import tensorflow_io as tfio
# Read the MNIST data into the IODataset.
+dataset_url = "http://storage.googleapis.com/cvdf-datasets/mnist/"
d_train = tfio.IODataset.from_mnist(
- 'http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz',
- 'http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz')
+ dataset_url + "train-images-idx3-ubyte.gz",
+ dataset_url + "train-labels-idx1-ubyte.gz",
+)
# Shuffle the elements of the dataset.
d_train = d_train.shuffle(buffer_size=1024)
@@ -38,17 +40,19 @@ d_train = d_train.map(lambda x, y: (tf.image.convert_image_dtype(x, tf.float32),
d_train = d_train.batch(32)
# Build the model.
-model = tf.keras.models.Sequential([
+model = tf.keras.models.Sequential(
+ [
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.2),
- tf.keras.layers.Dense(10, activation=tf.nn.softmax)
-])
+ tf.keras.layers.Dense(10, activation=tf.nn.softmax),
+ ]
+)
# Compile the model.
-model.compile(optimizer='adam',
- loss='sparse_categorical_crossentropy',
- metrics=['accuracy'])
+model.compile(
+ optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]
+)
# Fit the model.
model.fit(d_train, epochs=5, steps_per_epoch=200)

original_message: switch mnist dataset mirror to a more reliable one (#1327)


diff:
@@ -520,17 +520,13 @@ uint64_t Length(const TF_ReadOnlyMemoryRegion* region) {
// SECTION 4. Implementation for `TF_Filesystem`, the actual filesystem
// ----------------------------------------------------------------------------
-// TODO: use_multi_part_download=true will cause an issue when read request
-// is asking for bytes larger than object size. In that case, a status code of
-// 416 is returned. However, 416 is not handled correctly and s3 thought this
-// is an error - should return OUT_OF_RANGE with less bytes.
namespace tf_s3_filesystem {
S3File::S3File()
: s3_client(nullptr),
executor(nullptr),
transfer_managers(),
multi_part_chunk_sizes(),
- use_multi_part_download(false), // TODO: change to true after fix
+ use_multi_part_download(true),
initialization_lock() {}
void Init(TF_Filesystem* filesystem, TF_Status* status) {
filesystem->plugin_filesystem = new S3File();

diff:
set -e
set -o pipefail
-LOCALSTACK_VERSION=0.12.2
+LOCALSTACK_VERSION=0.12.10
docker pull localstack/localstack:$LOCALSTACK_VERSION
docker run -d --rm --net=host --name=tensorflow-io-aws localstack/localstack:$LOCALSTACK_VERSION
echo "Waiting for 10 secs until localstack is up and running"

original_message: bump `localstack` to `0.12.10` and set `use_multi_part_download` to `true` (#1378)


diff:
@@ -28,7 +28,7 @@ limitations under the License.
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/s3/model/HeadBucketRequest.h>
#include <aws/s3/model/HeadObjectRequest.h>
-#include <aws/s3/model/ListObjectsRequest.h>
+#include <aws/s3/model/ListObjectsV2Request.h>
#include <aws/s3/model/UploadPartCopyRequest.h>
#include <stdlib.h>
#include <string.h>
@@ -678,12 +678,12 @@ void Stat(const TF_Filesystem* filesystem, const char* path,
if (prefix.back() != '/') {
prefix.push_back('/');
}
- Aws::S3::Model::ListObjectsRequest list_objects_request;
+ Aws::S3::Model::ListObjectsV2Request list_objects_request;
list_objects_request.WithBucket(bucket).WithPrefix(prefix).WithMaxKeys(1);
list_objects_request.SetResponseStreamFactory(
[]() { return Aws::New<Aws::StringStream>(kS3FileSystemAllocationTag); });
auto list_objects_outcome =
- s3_file->s3_client->ListObjects(list_objects_request);
+ s3_file->s3_client->ListObjectsV2(list_objects_request);
if (list_objects_outcome.IsSuccess()) {
auto objects = list_objects_outcome.GetResult().GetContents();
if (objects.size() > 0) {
@@ -1074,12 +1074,12 @@ void DeleteDir(const TF_Filesystem* filesystem, const char* path,
GetS3Client(s3_file);
if (object.back() != '/') object.push_back('/');
- Aws::S3::Model::ListObjectsRequest list_objects_request;
+ Aws::S3::Model::ListObjectsV2Request list_objects_request;
list_objects_request.WithBucket(bucket).WithPrefix(object).WithMaxKeys(2);
list_objects_request.SetResponseStreamFactory(
[]() { return Aws::New<Aws::StringStream>(kS3FileSystemAllocationTag); });
auto list_objects_outcome =
- s3_file->s3_client->ListObjects(list_objects_request);
+ s3_file->s3_client->ListObjectsV2(list_objects_request);
if (list_objects_outcome.IsSuccess()) {
auto contents = list_objects_outcome.GetResult().GetContents();
if (contents.size() > 1 ||
@@ -1125,17 +1125,17 @@ void RenameFile(const TF_Filesystem* filesystem, const char* src,
}
Aws::S3::Model::DeleteObjectRequest delete_object_request;
- Aws::S3::Model::ListObjectsRequest list_objects_request;
+ Aws::S3::Model::ListObjectsV2Request list_objects_request;
list_objects_request.WithBucket(bucket_src)
.WithPrefix(object_src)
.WithMaxKeys(kS3GetChildrenMaxKeys);
list_objects_request.SetResponseStreamFactory(
[]() { return Aws::New<Aws::StringStream>(kS3FileSystemAllocationTag); });
- Aws::S3::Model::ListObjectsResult list_objects_result;
+ Aws::S3::Model::ListObjectsV2Result list_objects_result;
do {
auto list_objects_outcome =
- s3_file->s3_client->ListObjects(list_objects_request);
+ s3_file->s3_client->ListObjectsV2(list_objects_request);
if (!list_objects_outcome.IsSuccess())
return TF_SetStatusFromAWSError(list_objects_outcome.GetError(), status);
@@ -1155,7 +1155,8 @@ void RenameFile(const TF_Filesystem* filesystem, const char* src,
return TF_SetStatusFromAWSError(delete_object_outcome.GetError(),
status);
}
- list_objects_request.SetMarker(list_objects_result.GetNextMarker());
+ list_objects_request.SetContinuationToken(
+ list_objects_result.GetNextContinuationToken());
} while (list_objects_result.GetIsTruncated());
TF_SetStatus(status, TF_OK, "");
}
@@ -1171,7 +1172,7 @@ int GetChildren(const TF_Filesystem* filesystem, const char* path,
auto s3_file = static_cast<S3File*>(filesystem->plugin_filesystem);
GetS3Client(s3_file);
- Aws::S3::Model::ListObjectsRequest list_objects_request;
+ Aws::S3::Model::ListObjectsV2Request list_objects_request;
list_objects_request.WithBucket(bucket)
.WithPrefix(prefix)
.WithMaxKeys(kS3GetChildrenMaxKeys)
@@ -1179,11 +1180,11 @@ int GetChildren(const TF_Filesystem* filesystem, const char* path,
list_objects_request.SetResponseStreamFactory(
[]() { return Aws::New<Aws::StringStream>(kS3FileSystemAllocationTag); });
- Aws::S3::Model::ListObjectsResult list_objects_result;
+ Aws::S3::Model::ListObjectsV2Result list_objects_result;
std::vector<Aws::String> result;
do {
auto list_objects_outcome =
- s3_file->s3_client->ListObjects(list_objects_request);
+ s3_file->s3_client->ListObjectsV2(list_objects_request);
if (!list_objects_outcome.IsSuccess()) {
TF_SetStatusFromAWSError(list_objects_outcome.GetError(), status);
return -1;
@@ -1205,7 +1206,8 @@ int GetChildren(const TF_Filesystem* filesystem, const char* path,
result.push_back(entry);
}
}
- list_objects_result.SetMarker(list_objects_result.GetNextMarker());
+ list_objects_request.SetContinuationToken(
+ list_objects_result.GetNextContinuationToken());
} while (list_objects_result.GetIsTruncated());
int num_entries = result.size();

original_message: switch to `Aws::S3::Model::ListObjectsV2Request` (#1384)