From 600ab93dff2836fe31dcae96b679468b2e69d453 Mon Sep 17 00:00:00 2001 From: kbabushkin Date: Wed, 27 Mar 2024 15:35:54 -0700 Subject: [PATCH 1/2] v1 draft of the public rest API --- api/Makefile | 11 +- api/genpublic/v1/solaris.gen.go | 471 +++++++++++++++++++++++++ api/openapi/v1/solaris.yaml | 335 ++++++++++++++++++ api/openapi/v1/solaris_gen_config.yaml | 8 + pkg/intervals/intervals_test.go | 8 +- pkg/storage/buntdb/buntdb.go | 1 + pkg/storage/buntdb/buntdb_test.go | 1 + 7 files changed, 830 insertions(+), 5 deletions(-) create mode 100644 api/genpublic/v1/solaris.gen.go create mode 100644 api/openapi/v1/solaris.yaml create mode 100644 api/openapi/v1/solaris_gen_config.yaml diff --git a/api/Makefile b/api/Makefile index b5364ff..f70bd2e 100644 --- a/api/Makefile +++ b/api/Makefile @@ -2,11 +2,12 @@ PROTOC = protoc SOURCES = $(shell find . -name "*.proto") .PHONY: all -all: clean compile +all: clean compile solaris.v1 .PHONY: clean clean: @rm -rf gen/* + @rm -rf genpublic/v1/* .PHONY: compile compile: $(SOURCES:.proto=.go) @@ -15,3 +16,11 @@ compile: $(SOURCES:.proto=.go) @DIR=$(shell dirname $^);RESDIR=gen/;mkdir -p $$RESDIR;\ FNAME=$(shell basename $^);$(PROTOC) -I $$DIR --go-grpc_out=$$RESDIR --go_out=$$RESDIR $$FNAME +.PHONY: oapi-gen +oapi-gen: + @go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest + +.PHONY: solaris.v1 +solaris.v1: + @echo "generating go stubs for public Solaris REST API v1 ..." + @oapi-codegen --config openapi/v1/solaris_gen_config.yaml openapi/v1/solaris.yaml diff --git a/api/genpublic/v1/solaris.gen.go b/api/genpublic/v1/solaris.gen.go new file mode 100644 index 0000000..33e156b --- /dev/null +++ b/api/genpublic/v1/solaris.gen.go @@ -0,0 +1,471 @@ +// Package solarisapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT. +package solarisapi + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/gin-gonic/gin" +) + +// CreateLogRequest The request object to create log. +type CreateLogRequest struct { + // Tags The log tags. + Tags Tags `json:"tags"` +} + +// CreateRecordRequest The request object to create a record. +type CreateRecordRequest struct { + // Payload The record payload. + Payload []byte `json:"payload"` +} + +// CreateRecordsRequest The request object to create records. +type CreateRecordsRequest struct { + // Records The list of records to be created. + Records []CreateRecordRequest `json:"records"` +} + +// DeleteLogsRequest The request object to delete logs. +type DeleteLogsRequest struct { + // FilterCondition The filter condition. + FilterCondition string `json:"filterCondition"` +} + +// Log The log object. +type Log struct { + // CreatedAt The timestamp when the log was created. + CreatedAt time.Time `json:"createdAt"` + + // Id The log identifier. + Id string `json:"id"` + + // Records The number of records in the log. + Records int `json:"records"` + + // Tags The log tags. + Tags Tags `json:"tags"` + + // UpdatedAt The timestamp when the log was updated (new records added or tags are applied). + UpdatedAt time.Time `json:"updatedAt"` +} + +// QueryLogsResult The response object to the query logs request. +type QueryLogsResult struct { + // Items The list of found logs. + Items []Log `json:"items"` + + // NextPageId The id of the next page. + NextPageId *string `json:"nextPageId,omitempty"` + + // Total The total number of found logs. + Total int `json:"total"` +} + +// QueryRecordsRequest The request object to query records. +type QueryRecordsRequest struct { + // Desc If true, the result returns records with ids equal to or less than fromPageId. + Desc *bool `json:"desc,omitempty"` + + // FromPageId The fromPageId specifies from which page to start returning the results. + FromPageId *string `json:"fromPageId,omitempty"` + + // Limit The maximum number of records per page. + Limit *int `json:"limit,omitempty"` + + // LogIds The log ids filter. If specified, the logsFilterCondition is ignored. + LogIds *[]string `json:"logIds,omitempty"` + + // LogsFilterCondition The logs filter condition. + LogsFilterCondition *string `json:"logsFilterCondition,omitempty"` + + // RecsFilterCondition The records filter condition. + RecsFilterCondition *string `json:"recsFilterCondition,omitempty"` +} + +// QueryRecordsResult The response object to the query records request. +type QueryRecordsResult struct { + // Items The list of found records. + Items []Record `json:"items"` + + // NextPageId The id of the next page. + NextPageId *string `json:"nextPageId,omitempty"` + + // Total The total number of found records. + Total int `json:"total"` +} + +// Record The record object. +type Record struct { + // CreatedAt The timestamp when the record was created. + CreatedAt time.Time `json:"createdAt"` + + // Id The record identifier. + Id string `json:"id"` + + // LogId The log identifier. + LogId string `json:"logId"` + + // Payload The record payload. + Payload []byte `json:"payload"` +} + +// Tags The log tags. +type Tags map[string]string + +// UpdateLogRequest The request object to update log. +type UpdateLogRequest struct { + // Tags The log tags. + Tags Tags `json:"tags"` +} + +// FilterCondition defines model for FilterCondition. +type FilterCondition = string + +// FromPageId defines model for FromPageId. +type FromPageId = string + +// Limit defines model for Limit. +type Limit = int + +// LogId defines model for LogId. +type LogId = string + +// QueryLogsParams defines parameters for QueryLogs. +type QueryLogsParams struct { + // FilterCondition The filter condition. + FilterCondition *FilterCondition `form:"filterCondition,omitempty" json:"filterCondition,omitempty"` + + // FromPageId The id of the page to start returning the results from. + FromPageId *FromPageId `form:"fromPageId,omitempty" json:"fromPageId,omitempty"` + + // Limit The max number of objects to return per page. + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` +} + +// DeleteLogsJSONRequestBody defines body for DeleteLogs for application/json ContentType. +type DeleteLogsJSONRequestBody = DeleteLogsRequest + +// CreateLogJSONRequestBody defines body for CreateLog for application/json ContentType. +type CreateLogJSONRequestBody = CreateLogRequest + +// UpdateLogJSONRequestBody defines body for UpdateLog for application/json ContentType. +type UpdateLogJSONRequestBody = UpdateLogRequest + +// CreateRecordsJSONRequestBody defines body for CreateRecords for application/json ContentType. +type CreateRecordsJSONRequestBody = CreateRecordsRequest + +// QueryRecordsJSONRequestBody defines body for QueryRecords for application/json ContentType. +type QueryRecordsJSONRequestBody = QueryRecordsRequest + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Delete logs + // (DELETE /logs) + DeleteLogs(c *gin.Context) + // Query logs + // (GET /logs) + QueryLogs(c *gin.Context, params QueryLogsParams) + // Create log + // (POST /logs) + CreateLog(c *gin.Context) + // Update log + // (PUT /logs/{logId}) + UpdateLog(c *gin.Context, logId LogId) + // Create records + // (POST /logs/{logId}/records) + CreateRecords(c *gin.Context, logId LogId) + // Health check + // (GET /ping) + Ping(c *gin.Context) + // Query records + // (POST /records) + QueryRecords(c *gin.Context) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +type MiddlewareFunc func(c *gin.Context) + +// DeleteLogs operation middleware +func (siw *ServerInterfaceWrapper) DeleteLogs(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.DeleteLogs(c) +} + +// QueryLogs operation middleware +func (siw *ServerInterfaceWrapper) QueryLogs(c *gin.Context) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params QueryLogsParams + + // ------------- Optional query parameter "filterCondition" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filterCondition", c.Request.URL.Query(), ¶ms.FilterCondition) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter filterCondition: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "fromPageId" ------------- + + err = runtime.BindQueryParameter("form", true, false, "fromPageId", c.Request.URL.Query(), ¶ms.FromPageId) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fromPageId: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.QueryLogs(c, params) +} + +// CreateLog operation middleware +func (siw *ServerInterfaceWrapper) CreateLog(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.CreateLog(c) +} + +// UpdateLog operation middleware +func (siw *ServerInterfaceWrapper) UpdateLog(c *gin.Context) { + + var err error + + // ------------- Path parameter "logId" ------------- + var logId LogId + + err = runtime.BindStyledParameter("simple", false, "logId", c.Param("logId"), &logId) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter logId: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.UpdateLog(c, logId) +} + +// CreateRecords operation middleware +func (siw *ServerInterfaceWrapper) CreateRecords(c *gin.Context) { + + var err error + + // ------------- Path parameter "logId" ------------- + var logId LogId + + err = runtime.BindStyledParameter("simple", false, "logId", c.Param("logId"), &logId) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter logId: %s", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.CreateRecords(c, logId) +} + +// Ping operation middleware +func (siw *ServerInterfaceWrapper) Ping(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.Ping(c) +} + +// QueryRecords operation middleware +func (siw *ServerInterfaceWrapper) QueryRecords(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + } + + siw.Handler.QueryRecords(c) +} + +// GinServerOptions provides options for the Gin server. +type GinServerOptions struct { + BaseURL string + Middlewares []MiddlewareFunc + ErrorHandler func(*gin.Context, error, int) +} + +// RegisterHandlers creates http.Handler with routing matching OpenAPI spec. +func RegisterHandlers(router *gin.Engine, si ServerInterface) *gin.Engine { + return RegisterHandlersWithOptions(router, si, GinServerOptions{}) +} + +// RegisterHandlersWithOptions creates http.Handler with additional options +func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options GinServerOptions) *gin.Engine { + + errorHandler := options.ErrorHandler + + if errorHandler == nil { + errorHandler = func(c *gin.Context, err error, statusCode int) { + c.JSON(statusCode, gin.H{"msg": err.Error()}) + } + } + + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandler: errorHandler, + } + + router.DELETE(options.BaseURL+"/logs", wrapper.DeleteLogs) + + router.GET(options.BaseURL+"/logs", wrapper.QueryLogs) + + router.POST(options.BaseURL+"/logs", wrapper.CreateLog) + + router.PUT(options.BaseURL+"/logs/:logId", wrapper.UpdateLog) + + router.POST(options.BaseURL+"/logs/:logId/records", wrapper.CreateRecords) + + router.GET(options.BaseURL+"/ping", wrapper.Ping) + + router.POST(options.BaseURL+"/records", wrapper.QueryRecords) + + return router +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/8xYzY7bNhB+FYLtoQVU22ly8i3ZIOgCOWzS5BTkwJVGElOJ1JJUHGOhdy+G1D8pW+vd", + "LHIyLJLD+fnmmxne01iWlRQgjKb7e1oxxUowoOy/d7wwoK6kSLjhUuCnBHSseOX+0k85kNRuInG3a0Mj", + "ynHxrgZ1pBEVrAS6p+lMWER1nEPJUKo5VrhFG8VFRpsmou+ULG9YBtdJ+FaeEJkSkwOpWAbESKINU4Yo", + "MLUSXGR2TYGuC6NJqmS5qNdw02mV3vOSm7A2JftBRF3egkKt5O03iI1GpZw6pAJl9VzSobCiA9dzYSAD", + "5e6X2ZI3CpkRnoAwPOWg+lsqZvLRJfZ8RBXc1VxBQvdG1XDK5qZbtGi4UsAMvJfZR7irQS+4QrnF1gno", + "g9ieQx1RsUrJCpThYGUaltnf3xWkdE9/2w5w3LZXbz/hHtRlUPyLO/g16lR2t9EmarX8CLFUyWWKMqLs", + "aV/bih0LyZIleXiItHvwcCpVyQzd09ujARoFIDW2qBN+zih9mVVOPe0b1S4sAIujrLQ7jfJuoRVpbeQG", + "yrMRDMWk6c1kSrGj545OrZA73kIBFokP9UViDyIUA45IL2W703Gdiw0Z9F5my4nttvn6tlF4vWC94SVo", + "w8qKHHIQlg5R2oHpcfx6jCbMwF94xjcoonw173hHT8Jr4MwOYLzXdCStZ8HoAYQR0bpKLnRQe5L8IeDQ", + "q8aSBBIiFUEdCFNAWFUVHJI/1zpyhgyObGwNGtwUjcI6tiCEmg9YRFwWYJlbSgJdSaFhlAVoqS1ANhG6", + "NPEB1uf2Mi+kshZJn0+ryACx7iV/RAX8MOsKPu7sq6mHNyMNKxYCjksjzE2VD1TcSbCsbZ38xXBcxtEu", + "GosUjSJ8UdcpsQV81Om0/YbuMXvgJic80QTualbgVVKRArQmJmeCDL3PyAO3UhbABJqUnunChnWiK4iR", + "BVyzRQ45j/M1vVkwiMXJVouXdRmgjnGT5ROH7X70KRrTLblvyHXam5NEHTHoWTdMuCY8E1LNCqGPyBnS", + "A7IW1dJrCo5lj3UiO1+tK2NnIH4h6XQ6PJ53Rgmzinqc5r8U+4xMeDQBtead6k6frpdoBT5pO9HKPNNR", + "FA+Zg7zTP7WHtxW9m7O6m8YlPRS2T21LwxKXi6y4mc5IcxPCdmMjMbJ4kP/ZNhEPn9tc8/E8cxtu4yKV", + "VjY3Ba79KwumuH77hry+uaYR/Q5KO31fbHabHZomKxCs4nRPX252m5fW6Sa3mm2RPZ2p2PP7Rr+dzgJo", + "HcMlxNZowmgHZtDmjUyONlekMCCsG20DGNtj22/ake4wTp9yjj/CNFNXYWm3HxyTWlP+3r06USlKZuK8", + "q65zgicHUNDOP8nGhkXXZcnUceoK9GoGAYx86BtG31t9I2oDMLwgfQn7YNiynRetJjp/ZOhJVux2jzbN", + "V8+TuycL5bwNt4H0Y+SKH/KlruMYtE7rYh6IwcmWqmQoWa8mzynTQPRPND8Jtd4T0CrQvniy++3s0CxS", + "4KQYTT07eM0uWHbY3luqbuzTTh1w9ecJA05d3bPqgzHv3vEcJJ8+RB7brwrR7jlD1E61G8T4qxOUZjcL", + "aVyrNI/oEBw/otvRq8PJLBq1X6FM+thP5b9SiIOvgeszcXkqsDViSKDHRGfqXxehCvuX/X24vlzlEP9H", + "uGuyNajvoHDCqivCsE2uBQ6OfpxuUOYj4Rx49vYtRuXPsPc/wAqTkxgtcRafheGH+eAfKKwDCH8GmEKv", + "Fs9MGYGp8tE1dABe0/wfAAD//0Lk7CDlGgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/api/openapi/v1/solaris.yaml b/api/openapi/v1/solaris.yaml new file mode 100644 index 0000000..15fba86 --- /dev/null +++ b/api/openapi/v1/solaris.yaml @@ -0,0 +1,335 @@ +openapi: 3.0.3 +info: + title: 'SolarisDB API' + version: 1.0.0 +paths: + /logs: + post: + summary: Create log + description: Create log. + operationId: CreateLog + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateLogRequest' + responses: + 201: + description: The log was created. + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + get: + summary: Query logs + description: Query logs. + operationId: QueryLogs + parameters: + - $ref: '#/components/parameters/FilterCondition' + - $ref: '#/components/parameters/FromPageId' + - $ref: '#/components/parameters/Limit' + responses: + 200: + description: The query was successful. + content: + application/json: + schema: + $ref: '#/components/schemas/QueryLogsResult' + delete: + summary: Delete logs + description: Delete logs. + operationId: DeleteLogs + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteLogsRequest' + responses: + 204: + description: The logs matching the filter condition were deleted. + + /logs/{logId}: + put: + summary: Update log + description: Update log. + operationId: UpdateLog + parameters: + - $ref: '#/components/parameters/LogId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateLogRequest' + responses: + 200: + description: The log was updated. + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + 404: + description: The log was not found. + + /logs/{logId}/records: + post: + summary: Create records + description: Create records. + operationId: CreateRecords + parameters: + - $ref: '#/components/parameters/LogId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateRecordsRequest' + responses: + 201: + description: The records were created. + 404: + description: The log was not found. + + /records: + post: + summary: Query records + description: Query records. + operationId: QueryRecords + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/QueryRecordsRequest' + responses: + 200: + description: The query was successful. + content: + application/json: + schema: + $ref: '#/components/schemas/QueryRecordsResult' + + /ping: + get: + summary: Health check + description: Check if the server is up and running. + operationId: Ping + responses: + 200: + description: The ping was successful. + content: + application/json: + schema: + type: string + +components: + schemas: + Log: + type: object + description: The log object. + required: + - id + - tags + - records + - createdAt + - updatedAt + properties: + id: + type: string + description: The log identifier. + tags: + $ref: '#/components/schemas/Tags' + records: + type: integer + description: The number of records in the log. + createdAt: + type: string + description: The timestamp when the log was created. + format: date-time + updatedAt: + type: string + description: The timestamp when the log was updated (new records added or tags are applied). + format: date-time + + Tags: + type: object + description: The log tags. + additionalProperties: + type: string + + Record: + type: object + description: The record object. + required: + - id + - logId + - payload + - createdAt + properties: + id: + type: string + description: The record identifier. + logId: + type: string + description: The log identifier. + payload: + type: string + description: The record payload. + format: byte + createdAt: + type: string + description: The timestamp when the record was created. + format: date-time + + CreateLogRequest: + type: object + description: The request object to create log. + required: + - tags + properties: + tags: + $ref: '#/components/schemas/Tags' + + UpdateLogRequest: + type: object + description: The request object to update log. + required: + - tags + properties: + tags: + $ref: '#/components/schemas/Tags' + + QueryLogsResult: + type: object + description: The response object to the query logs request. + required: + - items + - total + properties: + items: + type: array + description: The list of found logs. + items: + $ref: '#/components/schemas/Log' + nextPageId: + type: string + description: The id of the next page. + total: + type: integer + description: The total number of found logs. + + DeleteLogsRequest: + type: object + description: The request object to delete logs. + required: + - filterCondition + properties: + filterCondition: + type: string + description: The filter condition. + + CreateRecordRequest: + type: object + description: The request object to create a record. + required: + - payload + properties: + payload: + type: string + description: The record payload. + format: byte + + CreateRecordsRequest: + type: object + description: The request object to create records. + required: + - records + properties: + records: + type: array + description: The list of records to be created. + items: + $ref: '#/components/schemas/CreateRecordRequest' + + QueryRecordsRequest: + type: object + description: The request object to query records. + properties: + logsFilterCondition: + type: string + description: The logs filter condition. + recsFilterCondition: + type: string + description: The records filter condition. + logIds: + type: array + description: The log ids filter. If specified, the logsFilterCondition is ignored. + items: + type: string + desc: + type: boolean + description: If true, the result returns records with ids equal to or less than fromPageId. + fromPageId: + type: string + description: The fromPageId specifies from which page to start returning the results. + limit: + type: integer + description: The maximum number of records per page. + + QueryRecordsResult: + type: object + description: The response object to the query records request. + required: + - items + - total + properties: + items: + type: array + description: The list of found records. + items: + $ref: '#/components/schemas/Record' + nextPageId: + type: string + description: The id of the next page. + total: + type: integer + description: The total number of found records. + + parameters: + # + # In path params + # + LogId: + in: path + name: logId + description: The log identifier. + required: true + schema: + type: string + + # + # Query params + # + FilterCondition: + in: query + name: filterCondition + description: The filter condition. + required: false + schema: + type: string + Limit: + in: query + name: limit + description: The max number of objects to return per page. + required: false + schema: + type: integer + FromPageId: + in: query + name: fromPageId + description: The id of the page to start returning the results from. + required: false + schema: + type: string diff --git a/api/openapi/v1/solaris_gen_config.yaml b/api/openapi/v1/solaris_gen_config.yaml new file mode 100644 index 0000000..93ff008 --- /dev/null +++ b/api/openapi/v1/solaris_gen_config.yaml @@ -0,0 +1,8 @@ +package: solarisapi +generate: + models: true + gin-server: true + embedded-spec: true +output-options: + skip-prune: true +output: genpublic/v1/solaris.gen.go diff --git a/pkg/intervals/intervals_test.go b/pkg/intervals/intervals_test.go index e644ad2..5ae7290 100644 --- a/pkg/intervals/intervals_test.go +++ b/pkg/intervals/intervals_test.go @@ -172,14 +172,14 @@ func TestInterval_NegateClosed(t *testing.T) { assert.Equal(t, b.Min, n1.L) assert.Equal(t, i.L, n1.R) - // [1, 1] + // [1, 2] i = b.Closed(1, 2) g := b.Negate(i) n1, n2 := g[0], g[1] assert.True(t, n1.IsOpenR()) // [min, 1) assert.Equal(t, b.Min, n1.L) assert.Equal(t, i.L, n1.R) - assert.True(t, n2.IsOpenL()) // (1, max] + assert.True(t, n2.IsOpenL()) // (2, max] assert.Equal(t, i.R, n2.L) assert.Equal(t, b.Max, n2.R) @@ -203,7 +203,7 @@ func TestInterval_IntersectOpen(t *testing.T) { assert.False(t, ok) i1 = b.Open(1, 3) // (1, 3) - i2 = b.Open(2, 4) // (2, 3) + i2 = b.Open(2, 4) // (2, 4) i3, ok = b.Intersect(i1, i2) // (2, 3) assert.True(t, ok) assert.True(t, i3.IsOpen()) @@ -274,7 +274,7 @@ func TestInterval_UnionOpen(t *testing.T) { assert.False(t, ok) i1 = b.Open(1, 3) // (1, 3) - i2 = b.Open(2, 4) // (2, 3) + i2 = b.Open(2, 4) // (2, 4) i3, ok = b.Union(i1, i2) // (1, 3) assert.True(t, ok) assert.True(t, i3.IsOpen()) diff --git a/pkg/storage/buntdb/buntdb.go b/pkg/storage/buntdb/buntdb.go index 22d70fc..0006f79 100644 --- a/pkg/storage/buntdb/buntdb.go +++ b/pkg/storage/buntdb/buntdb.go @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + package buntdb import ( diff --git a/pkg/storage/buntdb/buntdb_test.go b/pkg/storage/buntdb/buntdb_test.go index 53f6971..16ca8b0 100644 --- a/pkg/storage/buntdb/buntdb_test.go +++ b/pkg/storage/buntdb/buntdb_test.go @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + package buntdb import ( From 1c4bc51b91d19c823b0f8d06f9a5b86993d07e6b Mon Sep 17 00:00:00 2001 From: kbabushkin Date: Thu, 28 Mar 2024 21:03:54 -0700 Subject: [PATCH 2/2] do GET for /records --- api/genpublic/v1/solaris.gen.go | 175 +++++++++++++++++++++----------- api/openapi/v1/solaris.yaml | 71 +++++++------ 2 files changed, 152 insertions(+), 94 deletions(-) diff --git a/api/genpublic/v1/solaris.gen.go b/api/genpublic/v1/solaris.gen.go index 33e156b..36c6df4 100644 --- a/api/genpublic/v1/solaris.gen.go +++ b/api/genpublic/v1/solaris.gen.go @@ -73,27 +73,6 @@ type QueryLogsResult struct { Total int `json:"total"` } -// QueryRecordsRequest The request object to query records. -type QueryRecordsRequest struct { - // Desc If true, the result returns records with ids equal to or less than fromPageId. - Desc *bool `json:"desc,omitempty"` - - // FromPageId The fromPageId specifies from which page to start returning the results. - FromPageId *string `json:"fromPageId,omitempty"` - - // Limit The maximum number of records per page. - Limit *int `json:"limit,omitempty"` - - // LogIds The log ids filter. If specified, the logsFilterCondition is ignored. - LogIds *[]string `json:"logIds,omitempty"` - - // LogsFilterCondition The logs filter condition. - LogsFilterCondition *string `json:"logsFilterCondition,omitempty"` - - // RecsFilterCondition The records filter condition. - RecsFilterCondition *string `json:"recsFilterCondition,omitempty"` -} - // QueryRecordsResult The response object to the query records request. type QueryRecordsResult struct { // Items The list of found records. @@ -130,8 +109,8 @@ type UpdateLogRequest struct { Tags Tags `json:"tags"` } -// FilterCondition defines model for FilterCondition. -type FilterCondition = string +// Desc defines model for Desc. +type Desc = bool // FromPageId defines model for FromPageId. type FromPageId = string @@ -142,10 +121,40 @@ type Limit = int // LogId defines model for LogId. type LogId = string +// LogIds defines model for LogIds. +type LogIds = []string + +// LogsCondFilter defines model for LogsCondFilter. +type LogsCondFilter = string + +// RecordsCondFilter defines model for RecordsCondFilter. +type RecordsCondFilter = string + // QueryLogsParams defines parameters for QueryLogs. type QueryLogsParams struct { - // FilterCondition The filter condition. - FilterCondition *FilterCondition `form:"filterCondition,omitempty" json:"filterCondition,omitempty"` + // LogsCondFilter The condition for filtering the logs. + LogsCondFilter *LogsCondFilter `form:"logsCondFilter,omitempty" json:"logsCondFilter,omitempty"` + + // FromPageId The id of the page to start returning the results from. + FromPageId *FromPageId `form:"fromPageId,omitempty" json:"fromPageId,omitempty"` + + // Limit The max number of objects to return per page. + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` +} + +// QueryRecordsParams defines parameters for QueryRecords. +type QueryRecordsParams struct { + // LogsCondFilter The condition for filtering the logs. + LogsCondFilter *LogsCondFilter `form:"logsCondFilter,omitempty" json:"logsCondFilter,omitempty"` + + // RecordsCondFilter The condition for filtering the records. + RecordsCondFilter *RecordsCondFilter `form:"recordsCondFilter,omitempty" json:"recordsCondFilter,omitempty"` + + // LogIds The ids of the logs to consider. If specified, the `logsCondFilter` is ignored. + LogIds *LogIds `form:"logIds,omitempty" json:"logIds,omitempty"` + + // Desc The flag specifies the descending order for pagination. + Desc *Desc `form:"desc,omitempty" json:"desc,omitempty"` // FromPageId The id of the page to start returning the results from. FromPageId *FromPageId `form:"fromPageId,omitempty" json:"fromPageId,omitempty"` @@ -166,9 +175,6 @@ type UpdateLogJSONRequestBody = UpdateLogRequest // CreateRecordsJSONRequestBody defines body for CreateRecords for application/json ContentType. type CreateRecordsJSONRequestBody = CreateRecordsRequest -// QueryRecordsJSONRequestBody defines body for QueryRecords for application/json ContentType. -type QueryRecordsJSONRequestBody = QueryRecordsRequest - // ServerInterface represents all server handlers. type ServerInterface interface { // Delete logs @@ -190,8 +196,8 @@ type ServerInterface interface { // (GET /ping) Ping(c *gin.Context) // Query records - // (POST /records) - QueryRecords(c *gin.Context) + // (GET /records) + QueryRecords(c *gin.Context, params QueryRecordsParams) } // ServerInterfaceWrapper converts contexts to parameters. @@ -221,11 +227,11 @@ func (siw *ServerInterfaceWrapper) QueryLogs(c *gin.Context) { // Parameter object where we will unmarshal all parameters from the context var params QueryLogsParams - // ------------- Optional query parameter "filterCondition" ------------- + // ------------- Optional query parameter "logsCondFilter" ------------- - err = runtime.BindQueryParameter("form", true, false, "filterCondition", c.Request.URL.Query(), ¶ms.FilterCondition) + err = runtime.BindQueryParameter("form", true, false, "logsCondFilter", c.Request.URL.Query(), ¶ms.LogsCondFilter) if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter filterCondition: %s", err), http.StatusBadRequest) + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter logsCondFilter: %s", err), http.StatusBadRequest) return } @@ -317,11 +323,64 @@ func (siw *ServerInterfaceWrapper) Ping(c *gin.Context) { // QueryRecords operation middleware func (siw *ServerInterfaceWrapper) QueryRecords(c *gin.Context) { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params QueryRecordsParams + + // ------------- Optional query parameter "logsCondFilter" ------------- + + err = runtime.BindQueryParameter("form", true, false, "logsCondFilter", c.Request.URL.Query(), ¶ms.LogsCondFilter) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter logsCondFilter: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "recordsCondFilter" ------------- + + err = runtime.BindQueryParameter("form", true, false, "recordsCondFilter", c.Request.URL.Query(), ¶ms.RecordsCondFilter) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter recordsCondFilter: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "logIds" ------------- + + err = runtime.BindQueryParameter("form", true, false, "logIds", c.Request.URL.Query(), ¶ms.LogIds) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter logIds: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "desc" ------------- + + err = runtime.BindQueryParameter("form", true, false, "desc", c.Request.URL.Query(), ¶ms.Desc) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter desc: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "fromPageId" ------------- + + err = runtime.BindQueryParameter("form", true, false, "fromPageId", c.Request.URL.Query(), ¶ms.FromPageId) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fromPageId: %s", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %s", err), http.StatusBadRequest) + return + } + for _, middleware := range siw.HandlerMiddlewares { middleware(c) } - siw.Handler.QueryRecords(c) + siw.Handler.QueryRecords(c, params) } // GinServerOptions provides options for the Gin server. @@ -365,7 +424,7 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options router.GET(options.BaseURL+"/ping", wrapper.Ping) - router.POST(options.BaseURL+"/records", wrapper.QueryRecords) + router.GET(options.BaseURL+"/records", wrapper.QueryRecords) return router } @@ -373,29 +432,29 @@ func RegisterHandlersWithOptions(router *gin.Engine, si ServerInterface, options // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/8xYzY7bNhB+FYLtoQVU22ly8i3ZIOgCOWzS5BTkwJVGElOJ1JJUHGOhdy+G1D8pW+vd", - "LHIyLJLD+fnmmxne01iWlRQgjKb7e1oxxUowoOy/d7wwoK6kSLjhUuCnBHSseOX+0k85kNRuInG3a0Mj", - "ynHxrgZ1pBEVrAS6p+lMWER1nEPJUKo5VrhFG8VFRpsmou+ULG9YBtdJ+FaeEJkSkwOpWAbESKINU4Yo", - "MLUSXGR2TYGuC6NJqmS5qNdw02mV3vOSm7A2JftBRF3egkKt5O03iI1GpZw6pAJl9VzSobCiA9dzYSAD", - "5e6X2ZI3CpkRnoAwPOWg+lsqZvLRJfZ8RBXc1VxBQvdG1XDK5qZbtGi4UsAMvJfZR7irQS+4QrnF1gno", - "g9ieQx1RsUrJCpThYGUaltnf3xWkdE9/2w5w3LZXbz/hHtRlUPyLO/g16lR2t9EmarX8CLFUyWWKMqLs", - "aV/bih0LyZIleXiItHvwcCpVyQzd09ujARoFIDW2qBN+zih9mVVOPe0b1S4sAIujrLQ7jfJuoRVpbeQG", - "yrMRDMWk6c1kSrGj545OrZA73kIBFokP9UViDyIUA45IL2W703Gdiw0Z9F5my4nttvn6tlF4vWC94SVo", - "w8qKHHIQlg5R2oHpcfx6jCbMwF94xjcoonw173hHT8Jr4MwOYLzXdCStZ8HoAYQR0bpKLnRQe5L8IeDQ", - "q8aSBBIiFUEdCFNAWFUVHJI/1zpyhgyObGwNGtwUjcI6tiCEmg9YRFwWYJlbSgJdSaFhlAVoqS1ANhG6", - "NPEB1uf2Mi+kshZJn0+ryACx7iV/RAX8MOsKPu7sq6mHNyMNKxYCjksjzE2VD1TcSbCsbZ38xXBcxtEu", - "GosUjSJ8UdcpsQV81Om0/YbuMXvgJic80QTualbgVVKRArQmJmeCDL3PyAO3UhbABJqUnunChnWiK4iR", - "BVyzRQ45j/M1vVkwiMXJVouXdRmgjnGT5ROH7X70KRrTLblvyHXam5NEHTHoWTdMuCY8E1LNCqGPyBnS", - "A7IW1dJrCo5lj3UiO1+tK2NnIH4h6XQ6PJ53Rgmzinqc5r8U+4xMeDQBtead6k6frpdoBT5pO9HKPNNR", - "FA+Zg7zTP7WHtxW9m7O6m8YlPRS2T21LwxKXi6y4mc5IcxPCdmMjMbJ4kP/ZNhEPn9tc8/E8cxtu4yKV", - "VjY3Ba79KwumuH77hry+uaYR/Q5KO31fbHabHZomKxCs4nRPX252m5fW6Sa3mm2RPZ2p2PP7Rr+dzgJo", - "HcMlxNZowmgHZtDmjUyONlekMCCsG20DGNtj22/ake4wTp9yjj/CNFNXYWm3HxyTWlP+3r06USlKZuK8", - "q65zgicHUNDOP8nGhkXXZcnUceoK9GoGAYx86BtG31t9I2oDMLwgfQn7YNiynRetJjp/ZOhJVux2jzbN", - "V8+TuycL5bwNt4H0Y+SKH/KlruMYtE7rYh6IwcmWqmQoWa8mzynTQPRPND8Jtd4T0CrQvniy++3s0CxS", - "4KQYTT07eM0uWHbY3luqbuzTTh1w9ecJA05d3bPqgzHv3vEcJJ8+RB7brwrR7jlD1E61G8T4qxOUZjcL", - "aVyrNI/oEBw/otvRq8PJLBq1X6FM+thP5b9SiIOvgeszcXkqsDViSKDHRGfqXxehCvuX/X24vlzlEP9H", - "uGuyNajvoHDCqivCsE2uBQ6OfpxuUOYj4Rx49vYtRuXPsPc/wAqTkxgtcRafheGH+eAfKKwDCH8GmEKv", - "Fs9MGYGp8tE1dABe0/wfAAD//0Lk7CDlGgAA", + "H4sIAAAAAAAC/9RZT2/buBP9KgR/v8MuoLXdbU++tQmKLZBDmk1PRYAy4khmVyIVkmpqBP7uiyH1h7Io", + "W4mToHsKLJHDN/Nm3gyVB5qqslISpDV0/UArplkJFrT7dQ4mxb8cTKpFZYWSdE2vN0CyguXEVJCKTIAh", + "dgMEF4HkQuZEaQ6aZEqTiuVCMty4oAkVuP2uBr2lCZWsBLp2tmlCTbqBkuFhdlvh81ulCmCS7nYJ/ahV", + "ecly+MTjaAQnKnMgKpYDsYoYy7QlGmytJSLCdxpMXVhDMq3KKTRZf1IEk7FayNxBuhClsHE0JftJZF3e", + "gkZU6vY7pNYgKA+HVODiAlMYCmc6cryQFnLQ/nyVT0WjUDkRHKRFbnR3SsXsJjjE7U+ohrtaaOB0bXUN", + "R3zGPWaKAtNyUKjcuZsqaQQHvSCfsi5XeOLWfMNFZ0ryj6KwoL8RYYjIpdLAJ8PiTw8hCguliWBN2gdM", + "a7ZtsQfnxX1IleQCf7vUzdzKNnkQ7wFkoe3DQbyCVGl+EhbtTUzB0aMTDiHatS9dIM80MAsXKr+CuxrM", + "RIZr/7LJbce124dRQlSVVhVoK8CTw3L39/8aMrqm/1v2krNsjl5e4xrE0ufjV7/xpiPTn4bsepQ+kk8D", + "ypogjtFWbFsoxqfs4SbSrMHNmdIls6hXWws0iRAeetQaP+aUeZpXQWIMnWpeTOiFQFtZuxvt3UJj0ldj", + "W2aHGIxxEivEMBwtrFg4zqEAl4mPjQV3G7uCHQbCF9JZW1wTzc0t6ktwcZTXfbMxhy5UPq3XftkYb8PC", + "+wnvrSjBWFZW5H4DshUqcs9MyF+Xo5xZ+AP3jB1KqJjdTkZbD6ZX3wrbBBMd0sBa19ySRwhGQuuKPzFA", + "zU7ym4T7DhrjHDhRmiAGwjQQVlWFAP773EDuZYbAJusc6sOUBLSGHsSy5jNqu68CnF6misBUShoIqgA9", + "dX3Bt+OmTMYJ1tX2tC5kqpa8b4BzxABzfVT8CZXw086b43BlNySNu7uyrJggHF8FOTcEHxmkBmQ531r7", + "k3R0Gv1ERtpkO52UcBKYw4tH/ktRE7hwMjuNe4da9/MJbWPwWbW2sXlEbovHzP6j3S864Di5a+8W7Umh", + "3sVou270nnHfPllxORwg912I+40qG3jc2//iFPbxQ61X5tcZanGZkJlytoUt8N3fqmBamPMP5P3lJ5rQ", + "H6CNx/tmsVqs0DVVgWSVoGv6drFavHVBtxuHbImi513FgWjs9PlwUELv3E0dcysYv5pLIhj7QfGtqxUl", + "LUgXRtcdU7dt+d34gaq/axwKzni+2w1DhfdR98ArqXPlz9W7ybw3pGQ23bSXpP0hjtyDhmY45AtHi6nL", + "kuntMBQY1RwiOfK566bjaHVd2hHQf0L5Go9Bv2S5dy/dJUd3BB9DZqz23yl2N6NArp6Nyf0RxfE4psj3", + "PpRLU6cpGJPVxT4PfYydUqlYrZ4NrppDHrrr6wsl7eh6PCtn3zzb+W6u2k0q4KAXDSPbR829cOKwfHBK", + "vXPX3joS6i8DARyGuhPVp6Q85u7Ny1A0EvtZFK1ek6Jm4l9gjr87oGhusVTWT0r7jPbkjBldBjeyg1UU", + "TF+xSrrqbiy/EsXRLyXzK3Fq6jG+RfQFdAo7w/h6hiocX9YP8fZytoH0HyL8jG1A/wBNBOYKYTgl11IK", + "GSnDS7R5YjpHPgmOPUbwR9T7L2CF3ZAUPfEeB2l4oKdOJmF423qFzjr+NDunwfrP0jNWuv+m/Gcb/PDS", + "e3KP7wtjt/s3AAD//y33hbmFGgAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/openapi/v1/solaris.yaml b/api/openapi/v1/solaris.yaml index 15fba86..931c6f7 100644 --- a/api/openapi/v1/solaris.yaml +++ b/api/openapi/v1/solaris.yaml @@ -26,7 +26,7 @@ paths: description: Query logs. operationId: QueryLogs parameters: - - $ref: '#/components/parameters/FilterCondition' + - $ref: '#/components/parameters/LogsCondFilter' - $ref: '#/components/parameters/FromPageId' - $ref: '#/components/parameters/Limit' responses: @@ -93,16 +93,17 @@ paths: description: The log was not found. /records: - post: + get: summary: Query records description: Query records. operationId: QueryRecords - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/QueryRecordsRequest' + parameters: + - $ref: '#/components/parameters/LogsCondFilter' + - $ref: '#/components/parameters/RecordsCondFilter' + - $ref: '#/components/parameters/LogIds' + - $ref: '#/components/parameters/Desc' + - $ref: '#/components/parameters/FromPageId' + - $ref: '#/components/parameters/Limit' responses: 200: description: The query was successful. @@ -253,31 +254,6 @@ components: items: $ref: '#/components/schemas/CreateRecordRequest' - QueryRecordsRequest: - type: object - description: The request object to query records. - properties: - logsFilterCondition: - type: string - description: The logs filter condition. - recsFilterCondition: - type: string - description: The records filter condition. - logIds: - type: array - description: The log ids filter. If specified, the logsFilterCondition is ignored. - items: - type: string - desc: - type: boolean - description: If true, the result returns records with ids equal to or less than fromPageId. - fromPageId: - type: string - description: The fromPageId specifies from which page to start returning the results. - limit: - type: integer - description: The maximum number of records per page. - QueryRecordsResult: type: object description: The response object to the query records request. @@ -312,13 +288,36 @@ components: # # Query params # - FilterCondition: + LogsCondFilter: + in: query + name: logsCondFilter + description: The condition for filtering the logs. + required: false + schema: + type: string + RecordsCondFilter: in: query - name: filterCondition - description: The filter condition. + name: recordsCondFilter + description: The condition for filtering the records. required: false schema: type: string + LogIds: + in: query + name: logIds + description: The ids of the logs to consider. If specified, the `logsCondFilter` is ignored. + required: false + schema: + type: array + items: + type: string + Desc: + in: query + name: desc + description: The flag specifies the descending order for pagination. + required: false + schema: + type: boolean Limit: in: query name: limit