Skip to content

Commit

Permalink
APISHI-2357 Add support for Get/Post/Delete/Patch schemas for API Shi…
Browse files Browse the repository at this point in the history
…eld Schema Validation 2.0

This change adds support for the following API Shield related endpoints related to Schema Validation 2.0:

- Retrieve information about all schemas on a zone
- Retrieve information about a specific schema on a zone
- Upload a schema to a zone
- Enable validation for a schema
- Delete a schema
  • Loading branch information
djhworld committed Sep 19, 2023
1 parent 9406dff commit c8bfbf3
Show file tree
Hide file tree
Showing 3 changed files with 821 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1406.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
Add support for Get/Post/Delete/Patch schemas for API Shield Schema Validation 2.0
```
317 changes: 317 additions & 0 deletions api_shield_schemas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
package cloudflare

import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"strconv"
"time"

"github.com/goccy/go-json"
)

// APIShieldSchema represents a schema stored in API Shield Schema Validation 2.0.
type APIShieldSchema struct {
// ID represents the ID of the schema
ID string `json:"schema_id"`
// Name represents the name of the schema
Name string `json:"name"`
// Kind of the schema
Kind string `json:"kind"`
// Source is the contents of the schema
Source string `json:"source,omitempty"`
// CreatedAt is the time the schema was created
CreatedAt time.Time `json:"created_at,omitempty"`
// ValidationEnabled controls if schema is used for validation
ValidationEnabled bool `json:"validation_enabled,omitempty"`
}

// CreateAPIShieldSchemaParams represents the parameters to pass when creating a schema in Schema Validation 2.0.
//
// API documentation: TODO.
type CreateAPIShieldSchemaParams struct {
// Source is a io.Reader containing the contents of the schema
Source io.Reader
// Name represents the name of the schema.
Name string
// Kind of the schema. This is always set to openapi_v3.
Kind string
// ValidationEnabled controls if schema is used for validation
ValidationEnabled *bool
}

// GetAPIShieldSchemaParams represents the parameters to pass when retrieving a schema with a given schema ID.
//
// API documentation: TODO.
type GetAPIShieldSchemaParams struct {
// SchemaID is the ID of the schema to retrieve
SchemaID string `url:"-"`

// OmitSource specifies whether the contents of the schema should be returned in the "Source" field.
OmitSource *bool `url:"omit_source,omitempty"`
}

// ListAPIShieldSchemasParams represents the parameters to pass when retrieving all schemas.
//
// API documentation: TODO.
type ListAPIShieldSchemasParams struct {
// OmitSource specifies whether the contents of the schema should be returned in the "Source" field.
OmitSource *bool `url:"omit_source,omitempty"`

// ValidationEnabled specifies whether to return only schemas that have validation enabled.
ValidationEnabled *bool `url:"validation_enabled,omitempty"`

// PaginationOptions to apply to the request.
PaginationOptions
}

// DeleteAPIShieldSchemaParams represents the parameters to pass to delete a schema.
//
// API documentation: TODO.
type DeleteAPIShieldSchemaParams struct {
// SchemaID is the schema to be deleted
SchemaID string `url:"-"`
}

// PatchAPIShieldSchemaParams represents the parameters to pass to patch certain fields on an existing schema
//
// API documentation: TODO.
type PatchAPIShieldSchemaParams struct {
// SchemaID is the schema to be patched
SchemaID string `json:"-" url:"-"`

// ValidationEnabled controls if schema is used for validation
ValidationEnabled *bool `json:"validation_enabled" url:"-"`
}

// APIShieldGetSchemaResponse represents the response from the GET api_gateway/user_schemas/{id} endpoint.
type APIShieldGetSchemaResponse struct {
Result APIShieldSchema `json:"result"`
Response
}

// APIShieldListSchemasResponse represents the response from the GET api_gateway/user_schemas endpoint.
type APIShieldListSchemasResponse struct {
Result []APIShieldSchema `json:"result"`
ResultInfo `json:"result_info"`
Response
}

// APIShieldCreateSchemaResponse represents the response from the POST api_gateway/user_schemas endpoint.
type APIShieldCreateSchemaResponse struct {
Result APIShieldCreateSchemaResult `json:"result"`
Response
}

// APIShieldDeleteSchemaResponse represents the response from the DELETE api_gateway/user_schemas/{id} endpoint.
type APIShieldDeleteSchemaResponse struct {
Result interface{} `json:"result"`
Response
}

// APIShieldPatchSchemaResponse represents the response from the PATCH api_gateway/user_schemas/{id} endpoint.
type APIShieldPatchSchemaResponse struct {
Result APIShieldSchema `json:"result"`
Response
}

// APIShieldCreateSchemaResult represents the successful result of creating a schema in Schema Validation 2.0.
type APIShieldCreateSchemaResult struct {
// APIShieldSchema is the schema that was created
Schema APIShieldSchema `json:"schema"`
// APIShieldCreateSchemaEvents are non-critical event logs that occurred during processing.
Events APIShieldCreateSchemaEvents `json:"upload_details"`
}

// APIShieldCreateSchemaEvents are event logs that occurred during processing.
//
// The logs are separated into levels of severity.
type APIShieldCreateSchemaEvents struct {
Critical *APIShieldCreateSchemaEventWithLocation `json:"critical,omitempty"`
Errors []APIShieldCreateSchemaEventWithLocations `json:"errors,omitempty"`
Warnings []APIShieldCreateSchemaEventWithLocations `json:"warnings,omitempty"`
}

// APIShieldCreateSchemaEvent is an event log that occurred during processing.
type APIShieldCreateSchemaEvent struct {
// Code identifies the event that occurred
Code uint `json:"code"`
// Message describes the event that occurred
Message string `json:"message"`
}

// APIShieldCreateSchemaEventWithLocation is an event log that occurred during processing, with the location
// in the schema where the event occurred.
type APIShieldCreateSchemaEventWithLocation struct {
APIShieldCreateSchemaEvent

// Location is where the event occurred
// See https://goessner.net/articles/JsonPath/ for JSONPath specification.
Location string `json:"location,omitempty"`
}

// APIShieldCreateSchemaEventWithLocations is an event log that occurred during processing, with the location(s)
// in the schema where the event occurred.
type APIShieldCreateSchemaEventWithLocations struct {
APIShieldCreateSchemaEvent

// Locations lists JSONPath locations where the event occurred
// See https://goessner.net/articles/JsonPath/ for JSONPath specification
Locations []string `json:"locations"`
}

// GetAPIShieldSchema retrieves information about a specific schema on a zone
//
// API documentation: TODO.
func (api *API) GetAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params GetAPIShieldSchemaParams) (*APIShieldSchema, error) {
if params.SchemaID == "" {
return nil, fmt.Errorf("params.schemaID must be provided")
}

path := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

uri := buildURI(path, params)

res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}

var asResponse APIShieldGetSchemaResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return &asResponse.Result, nil
}

// ListAPIShieldSchemas retrieves all schemas for a zone
//
// API documentation: TODO.
func (api *API) ListAPIShieldSchemas(ctx context.Context, rc *ResourceContainer, params ListAPIShieldSchemasParams) ([]APIShieldSchema, ResultInfo, error) {
path := fmt.Sprintf("/zones/%s/api_gateway/user_schemas", rc.Identifier)

uri := buildURI(path, params)

res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, ResultInfo{}, err
}

var asResponse APIShieldListSchemasResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return asResponse.Result, asResponse.ResultInfo, nil
}

// CreateAPIShieldSchema uploads a schema to a zone
//
// API documentation: TODO.
func (api *API) CreateAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params CreateAPIShieldSchemaParams) (*APIShieldCreateSchemaResult, error) {
uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas", rc.Identifier)

if params.Name == "" {
return nil, fmt.Errorf("params.Name must not be empty")
}

if params.Source == nil {
return nil, fmt.Errorf("params.Source must not be nil")
}

// Prepare the form to be submitted
var b bytes.Buffer
w := multipart.NewWriter(&b)
// write fields
if err := w.WriteField("name", params.Name); err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}
if err := w.WriteField("kind", params.Kind); err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}

if params.ValidationEnabled != nil {
if err := w.WriteField("validation_enabled", strconv.FormatBool(*params.ValidationEnabled)); err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}
}

// write schema contents
part, err := w.CreateFormFile("file", params.Name)
if err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}
if _, err := io.Copy(part, params.Source); err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf("error during multi-part form construction: %w", err)
}

res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPost, uri, &b, http.Header{
"Content-Type": []string{w.FormDataContentType()},
})
if err != nil {
return nil, err
}

var asResponse APIShieldCreateSchemaResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return &asResponse.Result, nil
}

// DeleteAPIShieldSchema deletes a single schema
//
// API documentation: TODO.
func (api *API) DeleteAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params DeleteAPIShieldSchemaParams) error {
if params.SchemaID == "" {
return fmt.Errorf("params.schemaID must be provided")
}

uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return err
}

var asResponse APIShieldDeleteSchemaResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return nil
}

func (api *API) PatchAPIShieldSchema(ctx context.Context, rc *ResourceContainer, params PatchAPIShieldSchemaParams) (*APIShieldSchema, error) {
if params.SchemaID == "" {
return nil, fmt.Errorf("params.schemaID must be provided")
}

uri := fmt.Sprintf("/zones/%s/api_gateway/user_schemas/%s", rc.Identifier, params.SchemaID)

res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
if err != nil {
return nil, err
}

// Result should be the updated schema that was patched
var asResponse APIShieldPatchSchemaResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return &asResponse.Result, nil
}

0 comments on commit c8bfbf3

Please sign in to comment.