From b88dfe6db56e9d59fcc215d8226c91fcb20fadb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=A8=D1=83=D0=B1?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 20 Jun 2025 10:11:59 +0000 Subject: [PATCH] [hw-3] loms service --- .gitignore | 3 +- Makefile | 11 +- api/loms/v1/loms.proto | 125 ++ api/openapiv2/loms/v1/loms.swagger.json | 303 ++++ cart/Dockerfile | 10 +- cart/go.mod | 1 + cart/go.sum | 4 + cart/internal/app/app.go | 42 +- cart/internal/app/server/add_item_handler.go | 6 +- cart/internal/app/server/checkout_handler.go | 58 + cart/internal/app/server/server.go | 1 + cart/internal/clients/loms/service.go | 56 + .../products/service.go} | 2 +- cart/internal/domain/entity/cart.go | 2 +- cart/internal/domain/model/errors.go | 1 + .../repository/in_memory_repository.go | 0 .../in_memory_repository_bench_test.go | 0 .../repository/in_memory_repository_test.go | 0 .../service/mock/repository_mock.go | 0 .../domain/{cart => }/service/service.go | 51 +- .../domain/{cart => }/service/service_test.go | 205 ++- .../integration/cart_integration_test.go | 51 +- docker-compose.yaml | 15 +- docs/README.md | 1 + docs/homework-3/README.md | 262 +++ docs/homework-3/cart.http | 69 + .../img/cart-cart-checkout.plantuml | 18 + docs/homework-3/img/cart-cart-checkout.png | Bin 0 -> 23356 bytes .../img/cart-cart-item-add.plantuml | 32 + docs/homework-3/img/cart-cart-item-add.png | Bin 0 -> 48626 bytes .../homework-3/img/loms-order-cancel.plantuml | 18 + docs/homework-3/img/loms-order-cancel.png | Bin 0 -> 23803 bytes .../homework-3/img/loms-order-create.plantuml | 22 + docs/homework-3/img/loms-order-create.png | Bin 0 -> 34961 bytes docs/homework-3/img/loms-order-info.plantuml | 19 + docs/homework-3/img/loms-order-info.png | Bin 0 -> 24066 bytes docs/homework-3/img/loms-order-pay.plantuml | 17 + docs/homework-3/img/loms-order-pay.png | Bin 0 -> 21290 bytes docs/homework-3/img/loms-stok-info.plantuml | 15 + docs/homework-3/img/loms-stok-info.png | Bin 0 -> 16914 bytes docs/homework-3/loms.grpc | 189 +++ docs/homework-3/loms.http | 176 ++ docs/homework-3/stock-data.json | 37 + go.work | 9 +- go.work.sum | 42 + loms/Dockerfile | 22 + loms/cmd/server/main.go | 18 + loms/configs/values_local.yaml | 3 +- loms/go.mod | 24 + loms/go.sum | 72 + loms/internal/app/app.go | 119 ++ loms/internal/app/server/server.go | 148 ++ loms/internal/domain/entity/order.go | 18 + loms/internal/domain/entity/stock.go | 6 + loms/internal/domain/model/errors.go | 13 + .../repository/orders/in_memory_repository.go | 77 + .../repository/stocks/in_memory_repository.go | 114 ++ .../domain/repository/stocks/stock-data.json | 37 + .../service/mock/order_repository_mock.go | 1160 +++++++++++++ .../service/mock/stock_repository_mock.go | 1483 +++++++++++++++++ loms/internal/domain/service/service.go | 186 +++ loms/internal/domain/service/service_test.go | 448 +++++ loms/internal/infra/config/config.go | 74 + .../internal/infra/grpc/middleware/logging.go | 24 + .../infra/grpc/middleware/validate.go | 18 + .../integration/loms_integration_test.go | 130 ++ make/generate.mk | 85 + pkg/api/loms/v1/loms.pb.go | 827 +++++++++ pkg/api/loms/v1/loms.pb.gw.go | 491 ++++++ pkg/api/loms/v1/loms.pb.validate.go | 1111 ++++++++++++ pkg/api/loms/v1/loms_grpc.pb.go | 250 +++ pkg/go.mod | 18 + pkg/go.sum | 40 + 73 files changed, 8837 insertions(+), 52 deletions(-) create mode 100644 api/loms/v1/loms.proto create mode 100644 api/openapiv2/loms/v1/loms.swagger.json create mode 100644 cart/internal/app/server/checkout_handler.go create mode 100644 cart/internal/clients/loms/service.go rename cart/internal/{domain/products/service/product_service.go => clients/products/service.go} (98%) rename cart/internal/domain/{cart => }/repository/in_memory_repository.go (100%) rename cart/internal/domain/{cart => }/repository/in_memory_repository_bench_test.go (100%) rename cart/internal/domain/{cart => }/repository/in_memory_repository_test.go (100%) rename cart/internal/domain/{cart => }/service/mock/repository_mock.go (100%) rename cart/internal/domain/{cart => }/service/service.go (80%) rename cart/internal/domain/{cart => }/service/service_test.go (69%) create mode 100644 docs/homework-3/README.md create mode 100644 docs/homework-3/cart.http create mode 100644 docs/homework-3/img/cart-cart-checkout.plantuml create mode 100644 docs/homework-3/img/cart-cart-checkout.png create mode 100644 docs/homework-3/img/cart-cart-item-add.plantuml create mode 100644 docs/homework-3/img/cart-cart-item-add.png create mode 100644 docs/homework-3/img/loms-order-cancel.plantuml create mode 100644 docs/homework-3/img/loms-order-cancel.png create mode 100644 docs/homework-3/img/loms-order-create.plantuml create mode 100644 docs/homework-3/img/loms-order-create.png create mode 100644 docs/homework-3/img/loms-order-info.plantuml create mode 100644 docs/homework-3/img/loms-order-info.png create mode 100644 docs/homework-3/img/loms-order-pay.plantuml create mode 100644 docs/homework-3/img/loms-order-pay.png create mode 100644 docs/homework-3/img/loms-stok-info.plantuml create mode 100644 docs/homework-3/img/loms-stok-info.png create mode 100644 docs/homework-3/loms.grpc create mode 100644 docs/homework-3/loms.http create mode 100644 docs/homework-3/stock-data.json create mode 100644 loms/Dockerfile create mode 100644 loms/cmd/server/main.go create mode 100644 loms/go.sum create mode 100644 loms/internal/app/app.go create mode 100644 loms/internal/app/server/server.go create mode 100644 loms/internal/domain/entity/order.go create mode 100644 loms/internal/domain/entity/stock.go create mode 100644 loms/internal/domain/model/errors.go create mode 100644 loms/internal/domain/repository/orders/in_memory_repository.go create mode 100644 loms/internal/domain/repository/stocks/in_memory_repository.go create mode 100644 loms/internal/domain/repository/stocks/stock-data.json create mode 100644 loms/internal/domain/service/mock/order_repository_mock.go create mode 100644 loms/internal/domain/service/mock/stock_repository_mock.go create mode 100644 loms/internal/domain/service/service.go create mode 100644 loms/internal/domain/service/service_test.go create mode 100644 loms/internal/infra/config/config.go create mode 100644 loms/internal/infra/grpc/middleware/logging.go create mode 100644 loms/internal/infra/grpc/middleware/validate.go create mode 100644 loms/tests/integration/loms_integration_test.go create mode 100644 pkg/api/loms/v1/loms.pb.go create mode 100644 pkg/api/loms/v1/loms.pb.gw.go create mode 100644 pkg/api/loms/v1/loms.pb.validate.go create mode 100644 pkg/api/loms/v1/loms_grpc.pb.go create mode 100644 pkg/go.mod create mode 100644 pkg/go.sum diff --git a/.gitignore b/.gitignore index ddc19ad..553b044 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode bin .coverage -allure-results \ No newline at end of file +allure-results +vendor-proto/* \ No newline at end of file diff --git a/Makefile b/Makefile index 87b046e..eb6193d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ include make/generate.mk PKGS = $(shell go list ./... | grep -vE 'mock|config|generated|header|document|internal/pb') INTEGRATION_TAG = integration +.PHONY: lint, coverage + lint: cart-lint loms-lint notifier-lint comments-lint build: cart-build loms-build notifier-build comments-build @@ -13,15 +15,20 @@ build: cart-build loms-build notifier-build comments-build coverage: cart-coverage loms-coverage notifier-coverage comments-coverage generate: cart-generate loms-generate notifier-generate comments-generate + cd pkg && go mod tidy run-all: echo "starting build" docker-compose up --build +.PHONY: .integration-test integration-test: - ALLURE_OUTPUT_PATH=$(shell pwd) go test -v -tags=$(INTEGRATION_TAG) ./cart/tests/integration + ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=cart go test -v -tags=$(INTEGRATION_TAG) ./cart/tests/integration + ALLURE_OUTPUT_PATH=$(shell pwd)/allure-results ALLURE_OUTPUT_FOLDER=loms go test -v -tags=$(INTEGRATION_TAG) ./loms/tests/integration bench: go test -bench=BenchmarkInMemoryRepository -benchmem ./cart/internal/domain/cart/repository -benchtime 3s -.PHONY: lint, coverage +.PHONY: .serve-swagger +.serve-swagger: + bin/swagger serve api/openapiv2/loms/v1/loms.swagger.json --no-open diff --git a/api/loms/v1/loms.proto b/api/loms/v1/loms.proto new file mode 100644 index 0000000..f162350 --- /dev/null +++ b/api/loms/v1/loms.proto @@ -0,0 +1,125 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "validate/validate.proto"; +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "route256/pkg/api/loms/v1;loms"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "LOMS Service"; + version: "1.0.0"; + }; + schemes: HTTP; + schemes: HTTPS; + consumes: "application/json"; + produces: "application/json"; +}; + +service LOMS { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_tag) = { + description: "LOMS Service" + external_docs: { + url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/main/examples/internal/proto/examplepb/a_bit_of_everything.proto"; + description: "Find out more about grpc-gateway"; + } + }; + + rpc OrderCreate(OrderCreateRequest) returns (OrderCreateResponse) { + option(google.api.http) = { + post: "/order/create" + body: "*" + }; + } + + rpc OrderInfo(OrderInfoRequest) returns (OrderInfoResponse) { + option (google.api.http) = { + get: "/order/info" + }; + } + + rpc OrderPay(OrderPayRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/order/pay" + body: "*" + }; + } + + rpc OrderCancel(OrderCancelRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/order/cancel" + body: "*" + }; + } + + rpc StocksInfo(StocksInfoRequest) returns (StocksInfoResponse) { + option (google.api.http) = { + get: "/stock/info" + }; + } +} + +message OrderItem { + int64 sku = 1 [(validate.rules).int64 = {gt: 0}]; + uint32 count = 2 [(validate.rules).uint32 = {gt: 0}]; +} + +// OrderCreate + +message OrderCreateRequest { + int64 user_id = 1 [ + (validate.rules).int64 = {gt: 0} + ]; + repeated OrderItem items = 2 [ + (validate.rules).repeated = {min_items: 1} + ]; +} + +message OrderCreateResponse { + int64 orderId = 1; +} + +// OrderInfo + +message OrderInfoRequest { + int64 orderId = 1 [(validate.rules).int64 = {gt: 0}]; +} + +message OrderInfoResponse { + string status = 1; + int64 user_id = 2; + repeated OrderItem items = 3; +} + +enum OrderStatus { + ORDER_STATUS_UNSPECIFIED = 0; + ORDER_STATUS_NEW = 1; + ORDER_STATUS_AWAITING_PAYMENT = 2; + ORDER_STATUS_FAILED = 3; + ORDER_STATUS_PAYED = 4; + ORDER_STATUS_CANCELLED = 5; +} + +// OrderPay + +message OrderPayRequest { + int64 order_id = 1 [(validate.rules).int64 = {gt: 0}]; +} + +// OrderCancel + +message OrderCancelRequest { + int64 order_id = 1 [(validate.rules).int64 = {gt: 0}]; +} + +// StocksInfo + +message StocksInfoRequest { + int64 sku = 1 [(validate.rules).int64 = {gt: 0}]; +} + +message StocksInfoResponse { + uint32 count = 1; +} diff --git a/api/openapiv2/loms/v1/loms.swagger.json b/api/openapiv2/loms/v1/loms.swagger.json new file mode 100644 index 0000000..c093856 --- /dev/null +++ b/api/openapiv2/loms/v1/loms.swagger.json @@ -0,0 +1,303 @@ +{ + "swagger": "2.0", + "info": { + "title": "LOMS Service", + "version": "1.0.0" + }, + "tags": [ + { + "name": "LOMS", + "description": "LOMS Service", + "externalDocs": { + "description": "Find out more about grpc-gateway", + "url": "https://github.com/grpc-ecosystem/grpc-gateway/blob/main/examples/internal/proto/examplepb/a_bit_of_everything.proto" + } + } + ], + "schemes": [ + "http", + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/order/cancel": { + "post": { + "operationId": "LOMS_OrderCancel", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": {} + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/OrderCancelRequest" + } + } + ], + "tags": [ + "LOMS" + ] + } + }, + "/order/create": { + "post": { + "operationId": "LOMS_OrderCreate", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/OrderCreateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/OrderCreateRequest" + } + } + ], + "tags": [ + "LOMS" + ] + } + }, + "/order/info": { + "get": { + "operationId": "LOMS_OrderInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/OrderInfoResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "orderId", + "in": "query", + "required": false, + "type": "string", + "format": "int64" + } + ], + "tags": [ + "LOMS" + ] + } + }, + "/order/pay": { + "post": { + "operationId": "LOMS_OrderPay", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": {} + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/OrderPayRequest" + } + } + ], + "tags": [ + "LOMS" + ] + } + }, + "/stock/info": { + "get": { + "operationId": "LOMS_StocksInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/StocksInfoResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "sku", + "in": "query", + "required": false, + "type": "string", + "format": "int64" + } + ], + "tags": [ + "LOMS" + ] + } + } + }, + "definitions": { + "OrderCancelRequest": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "format": "int64" + } + } + }, + "OrderCreateRequest": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/OrderItem" + } + } + } + }, + "OrderCreateResponse": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "format": "int64" + } + } + }, + "OrderInfoResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "userId": { + "type": "string", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/OrderItem" + } + } + } + }, + "OrderItem": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "format": "int64" + }, + "count": { + "type": "integer", + "format": "int64" + } + } + }, + "OrderPayRequest": { + "type": "object", + "properties": { + "orderId": { + "type": "string", + "format": "int64" + } + } + }, + "StocksInfoResponse": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "format": "int64" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/cart/Dockerfile b/cart/Dockerfile index 051d65e..6848386 100644 --- a/cart/Dockerfile +++ b/cart/Dockerfile @@ -1,19 +1,21 @@ -FROM golang:1.23.1-alpine as builder +FROM golang:1.23.9-alpine as builder WORKDIR /build -COPY go.mod go.mod -COPY go.sum go.sum +COPY cart/go.mod go.mod +COPY cart/go.sum go.sum RUN go mod download COPY . . +WORKDIR cart + RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server/main.go FROM scratch COPY --from=builder server /bin/server -COPY configs/values_local.yaml /bin/config/values_local.yaml +COPY cart/configs/values_local.yaml /bin/config/values_local.yaml ENV CONFIG_FILE=/bin/config/values_local.yaml diff --git a/cart/go.mod b/cart/go.mod index 902f551..c1a72f3 100644 --- a/cart/go.mod +++ b/cart/go.mod @@ -5,6 +5,7 @@ go 1.23.9 require ( github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.10.0 + google.golang.org/grpc v1.72.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/cart/go.sum b/cart/go.sum index d738f81..86da82c 100644 --- a/cart/go.sum +++ b/cart/go.sum @@ -54,6 +54,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gojuno/minimock/v3 v3.4.5 h1:Jcb0tEYZvVlQNtAAYpg3jCOoSwss2c1/rNugYTzj304= github.com/gojuno/minimock/v3 v3.4.5/go.mod h1:o9F8i2IT8v3yirA7mmdpNGzh1WNesm6iQakMtQV6KiE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -147,6 +149,8 @@ go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCRE go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= diff --git a/cart/internal/app/app.go b/cart/internal/app/app.go index 3ce8ba5..0d4221e 100644 --- a/cart/internal/app/app.go +++ b/cart/internal/app/app.go @@ -9,14 +9,19 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "route256/cart/internal/app/server" - "route256/cart/internal/domain/cart/repository" - "route256/cart/internal/domain/cart/service" - product_service "route256/cart/internal/domain/products/service" + loms_service "route256/cart/internal/clients/loms" + product_service "route256/cart/internal/clients/products" + "route256/cart/internal/domain/repository" + "route256/cart/internal/domain/service" "route256/cart/internal/infra/config" "route256/cart/internal/infra/http/middlewares" "route256/cart/internal/infra/http/round_trippers" + + pbLOMS "route256/pkg/api/loms/v1" ) const ( @@ -39,9 +44,9 @@ func NewApp(configPath string) (*App, error) { zerolog.SetGlobalLevel(zerolog.InfoLevel) if c.Service.LogLevel != "" { - level, err := zerolog.ParseLevel(c.Service.LogLevel) - if err != nil { - return nil, fmt.Errorf("unknown log level `%s` provided: %w", c.Service.LogLevel, err) + level, logErr := zerolog.ParseLevel(c.Service.LogLevel) + if logErr != nil { + return nil, fmt.Errorf("unknown log level `%s` provided: %w", c.Service.LogLevel, logErr) } zerolog.SetGlobalLevel(level) @@ -52,7 +57,13 @@ func NewApp(configPath string) (*App, error) { app := &App{ config: c, } - app.server.Handler = app.BootstrapHandlers(app.setupCartService()) + + service, err := app.setupCartService() + if err != nil { + return nil, err + } + + app.server.Handler = app.BootstrapHandlers(service) return app, nil } @@ -70,7 +81,8 @@ func (app *App) ListenAndServe() error { return app.server.Serve(l) } -func (app *App) setupCartService() *service.CartService { +func (app *App) setupCartService() (*service.CartService, error) { + // Product service client transport := http.DefaultTransport transport = round_trippers.NewLogRoundTripper(transport) transport = round_trippers.NewRetryRoundTripper(transport, productsRetryAttemptsDefault, productsInitialDelaySecDefault) @@ -86,10 +98,21 @@ func (app *App) setupCartService() *service.CartService { fmt.Sprintf("%s:%s", app.config.ProductService.Host, app.config.ProductService.Port), ) + // LOMS service client + conn, err := grpc.NewClient( + fmt.Sprintf("%s:%s", app.config.LomsService.Host, app.config.LomsService.Port), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, fmt.Errorf("grpc.NewClient: %w", err) + } + + lomsClient := pbLOMS.NewLOMSClient(conn) + lomsService := loms_service.NewLomsService(lomsClient) + const userCartCap = 100 cartRepository := repository.NewInMemoryRepository(userCartCap) - return service.NewCartService(cartRepository, productService) + return service.NewCartService(cartRepository, productService, lomsService), nil } func (app *App) BootstrapHandlers(cartService *service.CartService) http.Handler { @@ -97,6 +120,7 @@ func (app *App) BootstrapHandlers(cartService *service.CartService) http.Handler mx := http.NewServeMux() mx.HandleFunc("POST /user/{user_id}/cart/{sku_id}", s.AddItemHandler) + mx.HandleFunc("POST /checkout/{user_id}", s.CheckoutHandler) mx.HandleFunc("GET /user/{user_id}/cart", s.GetItemsByUserIDHandler) mx.HandleFunc("DELETE /user/{user_id}/cart/{sku_id}", s.DeleteItemHandler) mx.HandleFunc("DELETE /user/{user_id}/cart", s.DeleteItemsByUserIDHandler) diff --git a/cart/internal/app/server/add_item_handler.go b/cart/internal/app/server/add_item_handler.go index 7d7e390..c07e87d 100644 --- a/cart/internal/app/server/add_item_handler.go +++ b/cart/internal/app/server/add_item_handler.go @@ -13,12 +13,12 @@ import ( "github.com/rs/zerolog/log" ) -type CreateReviewRequest struct { +type AddItemRequest struct { Count uint32 `json:"count" validate:"required,gt=0"` } func (s *Server) AddItemHandler(w http.ResponseWriter, r *http.Request) { - var request CreateReviewRequest + var request AddItemRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { makeErrorResponse(w, err, http.StatusBadRequest) @@ -79,7 +79,7 @@ func (s *Server) AddItemHandler(w http.ResponseWriter, r *http.Request) { if err := s.cartService.AddItem(r.Context(), uid, item); err != nil { switch { - case errors.Is(err, model.ErrProductNotFound): + case errors.Is(err, model.ErrProductNotFound), errors.Is(err, model.ErrNotEnoughStocks): makeErrorResponse(w, err, http.StatusPreconditionFailed) log.Trace().Err(err).Msgf("product does not exist") diff --git a/cart/internal/app/server/checkout_handler.go b/cart/internal/app/server/checkout_handler.go new file mode 100644 index 0000000..e187d2f --- /dev/null +++ b/cart/internal/app/server/checkout_handler.go @@ -0,0 +1,58 @@ +package server + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" + + "github.com/rs/zerolog/log" +) + +type CheckoutResponse struct { + OrderID int64 `json:"order_id"` +} + +func (s *Server) CheckoutHandler(w http.ResponseWriter, r *http.Request) { + strUserID := r.PathValue("user_id") + userID, err := strconv.ParseInt(strUserID, 10, 64) + if err != nil || userID <= 0 { + if err == nil { + err = fmt.Errorf("user_id must be greater than 0") + } + + makeErrorResponse(w, err, http.StatusBadRequest) + + log.Trace().Err(err).Msgf("user_id=`%s`", strUserID) + + return + } + + orderID, err := s.cartService.CheckoutUserCart(r.Context(), entity.UID(userID)) + switch { + case errors.Is(err, model.ErrCartNotFound): + makeErrorResponse(w, err, http.StatusNotFound) + + return + case err != nil: + makeErrorResponse(w, err, http.StatusInternalServerError) + + return + } + + resp := CheckoutResponse{ + OrderID: orderID, + } + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(resp); err != nil { + makeErrorResponse(w, err, http.StatusInternalServerError) + + return + } +} diff --git a/cart/internal/app/server/server.go b/cart/internal/app/server/server.go index 336ef09..8977830 100644 --- a/cart/internal/app/server/server.go +++ b/cart/internal/app/server/server.go @@ -12,6 +12,7 @@ type CartService interface { GetItemsByUserID(ctx context.Context, userID entity.UID) (*model.Cart, error) DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) error DeleteItemsByUserID(ctx context.Context, userID entity.UID) error + CheckoutUserCart(ctx context.Context, userID entity.UID) (int64, error) } type Server struct { diff --git a/cart/internal/clients/loms/service.go b/cart/internal/clients/loms/service.go new file mode 100644 index 0000000..dcfef2a --- /dev/null +++ b/cart/internal/clients/loms/service.go @@ -0,0 +1,56 @@ +package loms + +import ( + "context" + "fmt" + + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" + + pbLoms "route256/pkg/api/loms/v1" +) + +type Service struct { + grpcClient pbLoms.LOMSClient +} + +func NewLomsService(grpcClient pbLoms.LOMSClient) *Service { + return &Service{ + grpcClient: grpcClient, + } +} + +func (s *Service) OrderCreate(ctx context.Context, cart *model.Cart) (int64, error) { + items := make([]*pbLoms.OrderItem, len(cart.Items)) + for i, item := range cart.Items { + items[i] = &pbLoms.OrderItem{ + Sku: int64(item.Product.Sku), + Count: item.Count, + } + } + + req := &pbLoms.OrderCreateRequest{ + UserId: int64(cart.UserID), + Items: items, + } + + resp, err := s.grpcClient.OrderCreate(ctx, req) + if err != nil { + return 0, fmt.Errorf("grpcClient.OrderCreate: %w", err) + } + + return resp.OrderId, nil +} + +func (s *Service) StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error) { + req := &pbLoms.StocksInfoRequest{ + Sku: int64(sku), + } + + resp, err := s.grpcClient.StocksInfo(ctx, req) + if err != nil { + return 0, fmt.Errorf("grpcClient.StocksInfo: %w", err) + } + + return resp.Count, nil +} diff --git a/cart/internal/domain/products/service/product_service.go b/cart/internal/clients/products/service.go similarity index 98% rename from cart/internal/domain/products/service/product_service.go rename to cart/internal/clients/products/service.go index de3bcfe..d945700 100644 --- a/cart/internal/domain/products/service/product_service.go +++ b/cart/internal/clients/products/service.go @@ -1,4 +1,4 @@ -package service +package products import ( "context" diff --git a/cart/internal/domain/entity/cart.go b/cart/internal/domain/entity/cart.go index 0c6d2ca..1b7f317 100644 --- a/cart/internal/domain/entity/cart.go +++ b/cart/internal/domain/entity/cart.go @@ -1,7 +1,7 @@ package entity type ( - UID uint64 + UID int64 Sku int64 ) diff --git a/cart/internal/domain/model/errors.go b/cart/internal/domain/model/errors.go index 391a118..c01c17b 100644 --- a/cart/internal/domain/model/errors.go +++ b/cart/internal/domain/model/errors.go @@ -4,6 +4,7 @@ import "errors" var ( ErrProductNotFound = errors.New("invalid sku") + ErrNotEnoughStocks = errors.New("not enough stocks") ErrCartNotFound = errors.New("cart not found") ErrItemNotFoundInCart = errors.New("item not found in cart") diff --git a/cart/internal/domain/cart/repository/in_memory_repository.go b/cart/internal/domain/repository/in_memory_repository.go similarity index 100% rename from cart/internal/domain/cart/repository/in_memory_repository.go rename to cart/internal/domain/repository/in_memory_repository.go diff --git a/cart/internal/domain/cart/repository/in_memory_repository_bench_test.go b/cart/internal/domain/repository/in_memory_repository_bench_test.go similarity index 100% rename from cart/internal/domain/cart/repository/in_memory_repository_bench_test.go rename to cart/internal/domain/repository/in_memory_repository_bench_test.go diff --git a/cart/internal/domain/cart/repository/in_memory_repository_test.go b/cart/internal/domain/repository/in_memory_repository_test.go similarity index 100% rename from cart/internal/domain/cart/repository/in_memory_repository_test.go rename to cart/internal/domain/repository/in_memory_repository_test.go diff --git a/cart/internal/domain/cart/service/mock/repository_mock.go b/cart/internal/domain/service/mock/repository_mock.go similarity index 100% rename from cart/internal/domain/cart/service/mock/repository_mock.go rename to cart/internal/domain/service/mock/repository_mock.go diff --git a/cart/internal/domain/cart/service/service.go b/cart/internal/domain/service/service.go similarity index 80% rename from cart/internal/domain/cart/service/service.go rename to cart/internal/domain/service/service.go index 338f2f6..37b0d17 100644 --- a/cart/internal/domain/cart/service/service.go +++ b/cart/internal/domain/service/service.go @@ -6,10 +6,10 @@ import ( "slices" "sync" + "github.com/rs/zerolog/log" + "route256/cart/internal/domain/entity" "route256/cart/internal/domain/model" - - "github.com/rs/zerolog/log" ) //go:generate minimock -i Repository -o ./mock -s _mock.go @@ -24,15 +24,23 @@ type ProductService interface { GetProductBySku(ctx context.Context, sku entity.Sku) (*model.Product, error) } -type CartService struct { - repository Repository - productService ProductService +type LomsService interface { + OrderCreate(ctx context.Context, cart *model.Cart) (int64, error) + StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error) } -func NewCartService(repository Repository, productService ProductService) *CartService { +type CartService struct { + repository Repository + + productService ProductService + lomsService LomsService +} + +func NewCartService(repository Repository, productService ProductService, lomsService LomsService) *CartService { return &CartService{ repository: repository, productService: productService, + lomsService: lomsService, } } @@ -50,6 +58,15 @@ func (s *CartService) AddItem(ctx context.Context, userID entity.UID, item *mode return fmt.Errorf("productService.GetProductBySku: %w", err) } + count, err := s.lomsService.StocksInfo(ctx, item.Product.Sku) + if err != nil { + return fmt.Errorf("lomsService.StocksInfo: %w", err) + } + + if count < item.Count { + return model.ErrNotEnoughStocks + } + if err := s.repository.AddItem(ctx, userID, item); err != nil { return fmt.Errorf("repository.AddItemToCart: %w", err) } @@ -170,3 +187,25 @@ func (s *CartService) DeleteItemsByUserID(ctx context.Context, userID entity.UID return nil } + +func (s *CartService) CheckoutUserCart(ctx context.Context, userID entity.UID) (int64, error) { + if userID <= 0 { + return 0, fmt.Errorf("userID invalid") + } + + cart, err := s.GetItemsByUserID(ctx, entity.UID(userID)) + if err != nil { + return 0, err + } + + orderID, err := s.lomsService.OrderCreate(ctx, cart) + if err != nil { + return 0, fmt.Errorf("lomsService.OrderCreate: %w", err) + } + + if err := s.DeleteItemsByUserID(ctx, userID); err != nil { + return 0, err + } + + return orderID, nil +} diff --git a/cart/internal/domain/cart/service/service_test.go b/cart/internal/domain/service/service_test.go similarity index 69% rename from cart/internal/domain/cart/service/service_test.go rename to cart/internal/domain/service/service_test.go index 49aacf1..c42b168 100644 --- a/cart/internal/domain/cart/service/service_test.go +++ b/cart/internal/domain/service/service_test.go @@ -9,9 +9,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "route256/cart/internal/domain/cart/service/mock" "route256/cart/internal/domain/entity" "route256/cart/internal/domain/model" + "route256/cart/internal/domain/service/mock" ) const ( @@ -19,9 +19,9 @@ const ( validName = "some product name" ) -type ProductServiceFake struct{} +type productServiceFake struct{} -func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) { +func (f *productServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) { if sku%2 == 0 { return nil, errors.New("empty shelf") } @@ -33,6 +33,24 @@ func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) }, nil } +type lomsServiceFake struct{} + +func (f *lomsServiceFake) StocksInfo(_ context.Context, sku entity.Sku) (uint32, error) { + if sku == 1111 { + return 0, errors.New("stock error") + } + + return 100, nil +} + +func (f *lomsServiceFake) OrderCreate(_ context.Context, cart *model.Cart) (int64, error) { + if cart.UserID == 1111 { + return 0, errors.New("order create error") + } + + return 1234, nil +} + func TestCartService_AddItem(t *testing.T) { t.Parallel() @@ -49,9 +67,20 @@ func TestCartService_AddItem(t *testing.T) { Count: 1, } + testSKULomsFailing := entity.Sku(1111) + testItemLomsFailing := model.Item{ + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: testSKULomsFailing, + }, + Count: 1, + } + type fields struct { repository Repository productService ProductService + lomsService LomsService } type args struct { ctx context.Context @@ -72,7 +101,8 @@ func TestCartService_AddItem(t *testing.T) { AddItemMock. Expect(ctx, 1337, &testItem). Return(nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, }, args: args{ ctx: ctx, @@ -118,7 +148,8 @@ func TestCartService_AddItem(t *testing.T) { name: "product service error", fields: fields{ repository: nil, - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, }, args: args{ ctx: ctx, @@ -141,7 +172,8 @@ func TestCartService_AddItem(t *testing.T) { AddItemMock. Expect(ctx, 1337, &testItem). Return(assert.AnError), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, }, args: args{ ctx: ctx, @@ -150,6 +182,41 @@ func TestCartService_AddItem(t *testing.T) { }, wantErr: require.Error, }, + { + name: "stocks acquiring error", + fields: fields{ + repository: nil, + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, + }, + args: args{ + ctx: ctx, + item: &testItemLomsFailing, + userID: 1337, + }, + wantErr: require.Error, + }, + { + name: "not enough stocks", + fields: fields{ + repository: nil, + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, + }, + args: args{ + ctx: ctx, + item: &model.Item{ + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: testSKU, + }, + Count: 10000, + }, + userID: 1337, + }, + wantErr: require.Error, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -158,6 +225,7 @@ func TestCartService_AddItem(t *testing.T) { s := &CartService{ repository: tt.fields.repository, productService: tt.fields.productService, + lomsService: tt.fields.lomsService, } err := s.AddItem(tt.args.ctx, tt.args.userID, tt.args.item) @@ -218,7 +286,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) { GetItemsByUserIDMock. Expect(ctx, testUID). Return(testEntityCart, nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, }, args: args{ ctx: ctx, @@ -238,7 +306,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) { Items: []entity.Sku{testSKU}, ItemCount: map[entity.Sku]uint32{testSKU: 2}, }, nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, }, args: args{ ctx: ctx, @@ -271,7 +339,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) { Items: []entity.Sku{testSKU, 1}, ItemCount: map[entity.Sku]uint32{testSKU: 1, 1: 1}, }, nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, }, args: args{ ctx: ctx, @@ -357,7 +425,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) { Items: []entity.Sku{2}, ItemCount: map[entity.Sku]uint32{2: 1}, }, nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, }, args: args{ ctx: ctx, @@ -377,7 +445,7 @@ func TestCartService_GetItemsByUserID(t *testing.T) { Items: []entity.Sku{2}, ItemCount: map[entity.Sku]uint32{2: 1}, }, nil), - productService: &ProductServiceFake{}, + productService: &productServiceFake{}, }, args: args{ ctx: ctx, @@ -573,3 +641,118 @@ func TestCartService_DeleteItemsByUserID(t *testing.T) { }) } } + +func TestCartService_CheckoutUserCart(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + testSKU := entity.Sku(199) + testCart := entity.Cart{ + Items: []entity.Sku{testSKU}, + ItemCount: map[entity.Sku]uint32{testSKU: 1}, + } + + type fields struct { + repository Repository + productService ProductService + lomsService LomsService + } + + type args struct { + ctx context.Context + userID entity.UID + } + + tests := []struct { + name string + fields fields + args args + wantOrderID int64 + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, 1337). + Return(testCart, nil). + DeleteItemsByUserIDMock. + Expect(ctx, 1337). + Return(nil), + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, + }, + args: args{ctx: ctx, userID: 1337}, + wantOrderID: 1234, + wantErr: require.NoError, + }, + { + name: "invalid user id", + fields: fields{}, + args: args{ctx: ctx, userID: 0}, + wantErr: require.Error, + }, + { + name: "get cart error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, 1337). + Return(entity.Cart{}, assert.AnError), + }, + args: args{ctx: ctx, userID: 1337}, + wantErr: require.Error, + }, + { + name: "order create error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, 1111). + Return(testCart, nil), + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, + }, + args: args{ctx: ctx, userID: 1111}, + wantErr: require.Error, + }, + { + name: "delete order error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, 1337). + Return(testCart, nil). + DeleteItemsByUserIDMock. + Expect(ctx, 1337). + Return(assert.AnError), + productService: &productServiceFake{}, + lomsService: &lomsServiceFake{}, + }, + args: args{ctx: ctx, userID: 1337}, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := &CartService{ + repository: tt.fields.repository, + productService: tt.fields.productService, + lomsService: tt.fields.lomsService, + } + + orderID, err := svc.CheckoutUserCart(tt.args.ctx, tt.args.userID) + tt.wantErr(t, err) + if err == nil { + require.Equal(t, tt.wantOrderID, orderID) + } + }) + } +} diff --git a/cart/tests/integration/cart_integration_test.go b/cart/tests/integration/cart_integration_test.go index 16e59a0..df7ee57 100644 --- a/cart/tests/integration/cart_integration_test.go +++ b/cart/tests/integration/cart_integration_test.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net" "net/http" "net/http/httptest" "testing" @@ -16,13 +17,17 @@ import ( "github.com/ozontech/allure-go/pkg/framework/suite" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "google.golang.org/grpc" + lomsService "route256/cart/internal/adapter/client/loms" + productsService "route256/cart/internal/adapter/client/products" "route256/cart/internal/app" - "route256/cart/internal/domain/cart/repository" - cartService "route256/cart/internal/domain/cart/service" "route256/cart/internal/domain/entity" "route256/cart/internal/domain/model" - productsService "route256/cart/internal/domain/products/service" + "route256/cart/internal/domain/repository" + cartService "route256/cart/internal/domain/service" + + pbLoms "route256/pkg/api/loms/v1" ) const ( @@ -34,12 +39,27 @@ const ( testUID = entity.UID(1337) ) +type lomsServerMock struct { + pbLoms.UnimplementedLOMSServer +} + +func (lomsServerMock) OrderCreate(_ context.Context, _ *pbLoms.OrderCreateRequest) (*pbLoms.OrderCreateResponse, error) { + return &pbLoms.OrderCreateResponse{OrderId: 1}, nil +} + +func (lomsServerMock) StocksInfo(_ context.Context, _ *pbLoms.StocksInfoRequest) (*pbLoms.StocksInfoResponse, error) { + return &pbLoms.StocksInfoResponse{Count: 1000}, nil +} + type CartHandlerSuite struct { suite.Suite psContainer testcontainers.Container - server *httptest.Server + lomsSrv *grpc.Server + lomsConn *grpc.ClientConn + + server *httptest.Server cartSvc *cartService.CartService } @@ -72,6 +92,20 @@ func (s *CartHandlerSuite) BeforeAll(t provider.T) { productURL = endpoint }) + t.WithNewStep("start loms grpc server", func(st provider.StepCtx) { + lis, err := net.Listen("tcp", "127.0.0.1:0") + st.Require().NoError(err) + + s.lomsSrv = grpc.NewServer() + pbLoms.RegisterLOMSServer(s.lomsSrv, &lomsServerMock{}) + + go s.lomsSrv.Serve(lis) + + conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure(), grpc.WithBlock()) + st.Require().NoError(err) + s.lomsConn = conn + }) + t.WithNewStep("init cart-service", func(sCtx provider.StepCtx) { prodClient := productsService.NewProductService( *http.DefaultClient, @@ -79,8 +113,10 @@ func (s *CartHandlerSuite) BeforeAll(t provider.T) { productURL, ) + lomsClient := pbLoms.NewLOMSClient(s.lomsConn) + repo := repository.NewInMemoryRepository(10) - s.cartSvc = cartService.NewCartService(repo, prodClient) + s.cartSvc = cartService.NewCartService(repo, prodClient, lomsService.NewLomsService(lomsClient)) appSrv := &app.App{} @@ -89,8 +125,11 @@ func (s *CartHandlerSuite) BeforeAll(t provider.T) { } func (s *CartHandlerSuite) AfterAll(t provider.T) { - _ = s.psContainer.Terminate(context.Background()) s.server.Close() + s.lomsSrv.Stop() + s.lomsConn.Close() + + _ = s.psContainer.Terminate(context.Background()) } // DELETE /user//cart/ diff --git a/docker-compose.yaml b/docker-compose.yaml index 94bdcb4..539a8da 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,8 +1,8 @@ -version: "3.7" - services: cart: - build: cart/ + build: + context: . + dockerfile: cart/Dockerfile ports: - "8080:8080" depends_on: @@ -12,3 +12,12 @@ services: image: gitlab-registry.ozon.dev/go/classroom-18/students/homework-draft/products:latest ports: - "8082:8082" + + loms: + build: + context: . + dockerfile: loms/Dockerfile + ports: + - "8083:8083" + - "8084:8084" + - "8085:8085" diff --git a/docs/README.md b/docs/README.md index 714fe20..4ab939b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,3 +16,4 @@ 1. [Основы Go](./homework-1) 2. [Тестирование в Go](./homework-2) +3. [Межсервисное взаимодействие и основы эксплуатации](./homework-3) diff --git a/docs/homework-3/README.md b/docs/homework-3/README.md new file mode 100644 index 0000000..629211f --- /dev/null +++ b/docs/homework-3/README.md @@ -0,0 +1,262 @@ +# Домашнее задание по модулю "Межсервисное взаимодействие и основы эксплуатации" + +Добавить сервис LOMS и организовать взаимодействие между cart и loms с использованием gRPC. + +## Основное задание + +Необходимо: +- Имплементировать сервис, отвечающий за учет заказов и стоки по товарам. Логика работы методов и их контракты описаны ниже. +- Реализовать взаимодействие сервисов cart и loms через gRPC. + +Требование к решению: +- Создать protobuf контракт сервиса loms. +- В каждом проекте нужно добавить в Makefile команды для генерации .go файлов из proto файлов и установки нужных зависимостей (используем protoc). +- Состояние храним в in-memory, персистентное хранилище на данный момент не требуется. 2 репозитория - Stock и Order. +- Код должен быть покрыт тестами (тесты на методы репозитория - не требуются). +- Добавить gRPC интерцептор, который будет валидировать запросы через proto-gen-validator (правила валидации указываются в *.proto). +- Добавить HTTP-gateway. HTTP-запросы также должны проходить валидацию через добавленный выше gRPC интерцептор. + +## Дополнительное задание +- Добавить swagger-ui и возможность совершать запросы из swagger к сервису. +- Написать end-to-end тесты на все новые методы . + +## Спецификация LOMS (Logistics and Order Management System) + +Сервис отвечает за учет заказов и стоки по товарам. + +### OrderCreate + +Создает новый заказ для пользователя из списка переданных товаров с резервированием нужного количества стоков: ++ заказ получает статус "new" ++ резервирует нужное количество единиц товара ++ если удалось зарезервировать стоки, заказ получает статус "awaiting payment" ++ если не удалось зарезервировать стоки, заказ получает статус "failed", изменение стоков не происходит + +**Параметры ошибочных ответов:** + +| Сценарий | gRPC код ошибки (HTTP) | Описание | +|------------------------------------------------------------------------------|------------------------|---------------------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением userId | 3 (400) | Идентификатор пользователя должен быть натуральным числом (больше нуля) | +| Вызов c пустым списком товаров | 3 (400) | Идентификатор товара должен быть натуральным числом (больше нуля) | +| Вызов с нулевыми или отрицательными значениями sku в списке | 3 (400) | Количество должно быть натуральным числом (больше нуля) | +| Вызов с нулевыми или отрицательными значениями Count для любого sku в списке | 3 (400) | Count должен быть натуральным числом (больше нуля) | +| Превышение стоков хотя бы у одного товара | 9 (400) | Для всех товаров сток должен быть больше или равен запрашиваемому | +| Отсутствие информации по стокам в системе | 9 (400) | Невозможно создать заказ, если по хотя бы одному товару нет информации о стоках | +| Все остальные случаи | 13 или 2 (500) | Проблемы из-за неисправностей в системе | + +![loms-order-create](img/loms-order-create.png) + +Request +``` +{ + userId int64 + items []{ + sku int64 + count uint32 + } +} +``` + +Response +``` +{ + orderId int64 +} +``` + +### OrderInfo + +Показывает информацию по заказу. Товары в ответе должны быть отсортированы по SKU в порядке возрастания. + +**Параметры ошибочных ответов:** + +| Сценарий | gRPC код ошибки (HTTP) | Описание | +|-----------------------------------------------------|------------------------|---------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением orderId | 3 (400) | Идентификатор заказа должен быть натуральным числом (больше нуля) | +| Заказ с указанным orderId отсутствует в системе | 5 (404) | Можно получить информацию только для существующего в системе заказа | +| Все остальные случаи | 13 или 2 (500) | Проблемы из-за неисправностей в системе | + + +![loms-order-info](img/loms-order-info.png) + +Request +``` +{ + orderId int64 +} +``` + +Response +``` +{ + status string // (new | awaiting payment | failed | payed | cancelled) + userId int64 + items []{ + sku int64 + count uint32 + } +} +``` + +### OrderPay + +Помечает заказ оплаченным. Зарезервированные товары должны перейти в статус купленных. ++ удаляем зарезервированные стоки на товаре ++ заказ получает статус "payed" + +**Параметры ошибочных ответов:** + +| Сценарий | gRPC код ошибки (HTTP) | Описание | +|-----------------------------------------------------|------------------------|-------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением orderId | 3 (400) | Идентификатор заказа должен быть натуральным числом (больше нуля) | +| Оплата несуществующего заказа | 5 (404) | Можно оплачивать только существующий заказ | +| Оплата оплаченного заказа | 0 (200) | Оплата оплаченного заказа разрешается | +| Оплата заказа в статусе != "awaiting payment" | 9 (400) | Оплата заказа в невалидном статусе невозможна | +| Все остальные случаи | 13 или 2 (500) | Проблемы из-за неисправностей в системе | + +![loms-order-pay](img/loms-order-pay.png) + +Request +``` +{ + orderId int64 +} +``` + +Response +``` +{} +``` + +### OrderCancel + +Отменяет заказ, снимает резерв со всех товаров в заказе. ++ зарезервированные стоки на товаре становятся свободными стоками ++ заказ получает статус "cancelled" + +**Параметры ошибочных ответов:** + +| Сценарий | gRPC код ошибки (HTTP) | Описание | +|-----------------------------------------------------|------------------------|-------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением orderId | 3 (400) | Идентификатор заказа должен быть натуральным числом (больше нуля) | +| Отмена несуществующего заказа | 5 (404) | Можно отменять только существующий заказ | +| Отмена отмененного заказа | 0 (200) | Отмена отмененного заказа разрешается (идемпотентность) | +| Отмена заказа в статусе == "payed" или "failed" | 9 (400) | Невозможность отменить неудавшийся заказ, а также оплаченный | +| Все остальные случаи | 13 или 2 (500) | Проблемы из-за неисправностей в системе | + +![loms-order-cancel](img/loms-order-cancel.png) + +Request +``` +{ + orderId int64 +} +``` + +Response +``` +{} +``` + +### StocksInfo + +Возвращает количество товаров, которые можно купить. Если товар был зарезервирован у кого-то в заказе и ждет оплаты, его купить нельзя. +- данные по товарам берутся из stock-data.json (embed) +- структура stock: + - sku - товар + - total_count - всего товаров + - reserved - количество зарезервированных + +**Параметры ошибочных ответов:** + +| Сценарий | gRPC код ошибки (HTTP) | Описание | +|--------------------------------------------------|------------------------|--------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением sku | 3 (400) | Идентификатор товара должен быть натуральным числом (больше нуля) | +| Товара в запросе нет в базе стоков | 5 (404) | Можно получить информацию по стокам, если она есть в бд | +| Все остальные случаи | 13 или 2 (500) | Проблемы из-за неисправностей в системе | + +![loms-stok-info](img/loms-stok-info.png) + +Request +``` +{ + sku int64 +} +``` + +Response +``` +{ + count uint32 +} +``` + +## Доработки сервиса cart + +### POST /checkout/ + +Требуется добавить метод checkout - оформить заказ по всем товарам корзины. Вызывает loms.OrderCreate. +Сервис cart имеет HTTP-интерфейс. Взаимодействие с LOMS - через gRPC. + +**Параметры ошибочных ответов:** + +| Сценарий | HTTP код ошибки | Описание | +|-----------------------------------------------------|-----------------|-------------------------------------------------------------------------| +| Вызов с нулевым или отрицательным значением user_id | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | +| Вызов для пустой корзины | 404 | Невозможно оформить заказ для пустой корзины | +| Все остальные случаи | 500 | Проблемы из-за неисправностей в системе | + +![cart-cart-checkout](img/cart-cart-checkout.png) + +Request +``` +POST /checkout/ (user_id - int64) +``` + +Response +``` +{ + order_id int64 +} +``` + +### POST /user//cart/ + +Требуется добавить запрос в метод добавления товаров в корзину на проверку наличия стоков с помощью вызова gRPC метода loms.StocksInfo. + +**Параметры ошибочных ответов:** +Сценарии из прошлых домашних заданий без изменений. + +| Сценарий | HTTP код ошибки | Описание | +|-----------------------------------------|-----------------|--------------------------------------------------------------------| +| Превышение стоков при добавлении товара | 412 | Невозможно добавить товара по количеству больше, чем есть в стоках | +| Все остальные случаи | 500 | Проблемы из-за неисправностей в системе | + +![cart-cart-item-add](img/cart-cart-item-add.png) + +# Путь покупки товаров: + +- /user/{user_id}/cart/{sku_id} - Добавляем товар в корзину с проверкой на наличие стоков. +- /user/{user_id}/cart || /user/{user_id}/cart/{sku_id} - Можем удалять товары из корзины. +- /user/{user_id}/cart - Можем получить состав корзины. +- /checkout/{user_id} - Создаем заказ по товарам из корзины. +- /order/pay with body { "orderId": {order_id} } - Оплачиваем заказ. +- /order/cancel with body { "orderId": {order_id} } - Можем отменить заказ до оплаты. + +### Примечания +* e2e тесты проверяют HTTP коды ошибок, однако gRPC коды должны быть те, что указаны в требованиях. Например, могут быть проблемы с codes.FailedPrecondition, подробнее [тут](https://github.com/grpc-ecosystem/grpc-gateway/blob/main/runtime/errors.go). +* Запросы из cart.http, loms.http & loms.grpc основаны на данных, что лежат в stock-data.json + +## Автоматические проверки + +Ваше решение должно проходить автоматические проверки: + +- Компиляция +- Линтер +- Unit-тесты (если есть) +- Автотесты + +Прохождение автоматических проверок влияет на итоговую оценку за домашнюю работу. + +### Дедлайны сдачи и проверки задания: +- 7 июня 23:59 (сдача) / 10 июня, 23:59 (проверка) diff --git a/docs/homework-3/cart.http b/docs/homework-3/cart.http new file mode 100644 index 0000000..cb39272 --- /dev/null +++ b/docs/homework-3/cart.http @@ -0,0 +1,69 @@ +### check cart state, expect empty cart +GET http://localhost:8080/user/31337/cart +Content-Type: application/json + +### expected: 404 (Not Found) {} + + +### add item to cart, see available stocks in loms db +POST http://localhost:8080/user/31337/cart/1076963 +Content-Type: application/json + +{ + "count": 1 +} +### expected: 200 (OK) {} + + +### check cart state, expect single sku in cart +GET http://localhost:8080/user/31337/cart + +### expected: 200 (OK) {"items":[{"sku":1076963,"count":1,"name":"Теория нравственных чувств | Смит Адам","price":3379}],"totalPrice":3379} + + +######################### +### checkout cart +POST http://localhost:8080/checkout/31337 +Content-Type: application/json + +### expected: 200 (OK) {"order_id":1} + + +### check orderID in LOMS +GET http://localhost:8084/order/info?orderId=1 +Content-Type: application/json +### expected: 200 (OK) {"status":"awaiting payment","user":31337,"Items":[{"sku":1076963,"count":1}]} + + +### check cart, expect empty +GET http://localhost:8080/user/31337/cart +Content-Type: application/json + +### expected: 404 (Not Found) {} + + +### add unknown item, expect error +POST http://localhost:8080/user/31337/cart/404 +Content-Type: application/json + +{ + "count": 1 +} +### expected: 412 (Precondition Failed) {} + + +### add item out of stock, expect error +POST http://localhost:8080/user/31337/cart/135937324 +Content-Type: application/json + +{ + "count": 65534 +} +### expected: 412 (Precondition Failed) {} + + +### checkout empty cart, expect error +POST http://localhost:8080/checkout/31337 +Content-Type: application/json + +### expected: 404 (Not Found) {} diff --git a/docs/homework-3/img/cart-cart-checkout.plantuml b/docs/homework-3/img/cart-cart-checkout.plantuml new file mode 100644 index 0000000..b803058 --- /dev/null +++ b/docs/homework-3/img/cart-cart-checkout.plantuml @@ -0,0 +1,18 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs +collections Order as o + +u -> c : POST /checkout/ +activate c +c -> cs : cart.GetItemsByUserID +c -> o : gRPC Loms.OrderCreate\n\t- user\n\t- []item +c -> cs : cart.DeleteItemsByUserID + +c -> u : Response: 200 OK + +deactivate c + +@enduml diff --git a/docs/homework-3/img/cart-cart-checkout.png b/docs/homework-3/img/cart-cart-checkout.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed45bb30581333048a4c9b5250317ae659cd9e9 GIT binary patch literal 23356 zcmcG$2RzpO`!`JXUfJ0qr%fdk**ly@L}im1$(Frk&yaA+EUU=KURfcOtWt<gu|#|L?i~_x;@W^E|y?znANGI(_HyIga=HeY}rPgw{1B5<&(-3=9ks6=iuO2F58; z3=B*w{8R8rWJ*Xo{6M%W7`U1{-SxD$v~tBzvUId`F?F@HU^Vk(wQ+U5drv}8@UFe7 zqw9SKdjWGN2e*M97W5(R=o+~G`5Xfij^mMeQ(MjMlrVYY!u39FvK#LF$Q1TjN8Uy) zpC4Lk5#vQ9QyQG<_{RqW&y&WcTYcW96JJ(Q%Ha!+i!2R#ri3>vN8Uq&gBTw3P)^s? zNbWxSCTQCrcC0i`sg%Nx!{F87l6eUJ;8IW^c1qL_+|?28VnN1ltHO83>IV+=UP!6Q z?p(hxE^6d_)jRk+7vIr3lA6)_`lN`DF~3gQ_;Ahl(h)Ys<^IPvw0_>YUFP&?^p}Px zU)=iGZV!RFVUF|$VcONn#bz^ve2gDqB<7T@N8&?&OhMjXp2NC&@m=j%3`uC-lc#AP)D9J#u$haCj{oEgW& zpWbA?ta>UhXI7c3qZPYTv*|d~6wV?3NBiM^^km*6WW z<@Fvh^sy@W7vm$_!pLIJ{2cwcB_*fN4wxa``dmrIDhJsMPJncQwvmo=#6sU+eT|y4B7~xv_}|f04*sME!UF-(kh# z&#i?`FM=)Q0$ri@nG$?b(wrH%+t%63zWdu@ELM7Dx3K7E-oJNh_BZ9#muHepDLuU0 zW+THnTR~l|KyrMXtj)NTBqoPa3`NO)op5SO&VaiV>3n{(zF44Phl&!BUZe6S z4_*XTf9NnTyg$}Nf(&QBwFi*JTNRtAAI<(e5Tg&R>T_xfRrT4R$_ zYS7`T7O!Ynk&43u_h; zGbM%kD>p~Djsv+>LVo@}n+38()ok|JT(95S@Y#{x?@hN`?B1_dJvhQ;Jfd!p2~xzy zxlitgLuM2b8v4wr<|Y^C--j1MP!Cs{cjc@#WrvN-l$%m%i(5343Yx(}v^>j6edi=V zNaBliyL;Xwrkb0_mfdD~sIgAlTYj!BnkIp83!PYG<;$v({jho_BgWbKlFjRov51f!*?VTY1VfFA!y= zkCSF8<4{((_?_3|6Qqu1zOp~_>o}~*1uw^28J3q=zxFxu&nDqImKQ>7VtGcWhAQ35i zhU`~*3_=xVxwpf&?Ryj0r!W&D?Zc?mLUybv)7hyr#GG!u_IlZePqZw;k;4n;j=qYF z59!!MQn0W{d+*rSj$%M=d=~5oF)wmTMS+HfNUftIinc43MX*NIpH|p~H;3%+KTAM5 z|2jVU+!h9Zs=gYB0gs%=om|2n?j66PqC$D5N^rZG=Nr6zegnl>SFc>vb^(sRFO(`;DHzC@8YfMTqQ8NsNpA!2ce~0grWUk|&G9;&$h2UwW0-k4M77N=c?qRaYl> zu6T#?uG~S;ClE$8RLJ@K!ZZDmmJq3t;WpQkEa`WAs8cX7U9(0f5KY0+dHuPu9{n4^ z8DFsi&1_87dh{uS#;dF)B_+k|#(WkAcP;>Z9`x5t+~BdK`<(lJk) zeSE>PHP?H+Q0w!nSMPdE^3F!(&vrg!r12zU^)6wxO_anhX-m>Q@9^<;Dt?m{$c?_m_BYmYNg~J(2lx3Ww0lWVGp**3fjLZ+90<4Wrm9qqTMF^y`D& z?_%vOEtpkQf@PZyAF+A9tFRT73p|$cm4NE(?~lRF&F$y+3le9k0fpr;lao9qDwBEJn(H2#*4}0y{Tns(teoLeD≤_j;w(P^Hi1y{q>ItK(& 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 VdLGmkeFqq80aGIj!(6;8{V!Y_a9aQX literal 0 HcmV?d00001 diff --git a/docs/homework-3/img/cart-cart-item-add.plantuml b/docs/homework-3/img/cart-cart-item-add.plantuml new file mode 100644 index 0000000..03537c7 --- /dev/null +++ b/docs/homework-3/img/cart-cart-item-add.plantuml @@ -0,0 +1,32 @@ +@startuml + +actor User as u +collections Cart as c +database CartStorage as cs +collections ProductService as p +collections Order as o + +u -> c : POST /user//cart/\n\t- count +activate c +loop for each item in request + c -> p : /get_product\n\t- sku\n\t- token + activate p + p -> c : \nResponse:\n\t- name\n\t- price + deactivate p + c -> c : validate product exists + + c -> o : gRPC Loms.StocksInfo\n\t- sku + activate o + o -> c : Response:\n\t- count + deactivate o + c -> c : validate stocks + alt stocks enough + c -> cs : cart.AddItem() + c -> u : 200 OK + else + c -> u : 412 Failed Precondition (insufficient stocks) + end +end +deactivate c + +@enduml diff --git a/docs/homework-3/img/cart-cart-item-add.png b/docs/homework-3/img/cart-cart-item-add.png new file mode 100644 index 0000000000000000000000000000000000000000..1127d90982400249814320ad7e96dbc778b898cc GIT binary patch literal 48626 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 literal 0 HcmV?d00001 diff --git a/docs/homework-3/img/loms-order-cancel.plantuml b/docs/homework-3/img/loms-order-cancel.plantuml new file mode 100644 index 0000000..5df2c4f --- /dev/null +++ b/docs/homework-3/img/loms-order-cancel.plantuml @@ -0,0 +1,18 @@ +@startuml + +actor User as u +collections LOMS as l +database OrdersStorage as os +database StocksStorage as ss + +u -> l : gRPC Loms.OrderCancel\n\t- orderID +activate l + +l -> os : order.GetByOrderID() +l -> ss : stocks.ReserveCancel() +l -> os : order.SetStatus(cancelled) +deactivate l + +l -> u : Response: OK (code_id=0) + +@enduml diff --git a/docs/homework-3/img/loms-order-cancel.png b/docs/homework-3/img/loms-order-cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..02a63f34dedda73d046113f91a2b578eb6b88d9a GIT binary patch literal 23803 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%0AYcR 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 l : gRPC Loms.OrderCreate\n\t- user\n\t- items: []item{\n\t\t- sku\n\t\t- count\n\t } +activate l +l -> os : order.Create()\n\tstatus=new +l -> ss : stocks.Reserve() + +alt Reserve success + l -> os : order.SetStatus(awaiting payment) + l -> u : Response: OK (code_id=0)\n\t- orderID +else + l -> os : order.SetStatus(failed) + l -> u : Failed Precondition (code_id 9) +end +deactivate l + +@enduml diff --git a/docs/homework-3/img/loms-order-create.png b/docs/homework-3/img/loms-order-create.png new file mode 100644 index 0000000000000000000000000000000000000000..f25f8760c554b70ee3c0454b3cafd4ce8864422d GIT binary patch literal 34961 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 literal 0 HcmV?d00001 diff --git a/docs/homework-3/img/loms-order-info.plantuml b/docs/homework-3/img/loms-order-info.plantuml new file mode 100644 index 0000000..ec875d8 --- /dev/null +++ b/docs/homework-3/img/loms-order-info.plantuml @@ -0,0 +1,19 @@ +@startuml + +actor User as u +collections LOMS as l +database OrdersStorage as os + +u -> l : gRPC Loms.OrderInfo\n\t- orderID int64 +activate l + +l -> os : order.GetByID() +alt order exists + l -> u : Response: OK (code_id=0)\n\t- status\n\t- user\n\t- []items{} +else + l -> u : Response: Not Found (code_id=5) +end + +deactivate l + +@enduml diff --git a/docs/homework-3/img/loms-order-info.png b/docs/homework-3/img/loms-order-info.png new file mode 100644 index 0000000000000000000000000000000000000000..67250e9cc6859f92534ca75d1cdf2b3d6af4b5ee GIT binary patch literal 24066 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 yzC~$ZbOk(+#{gh6#MF(?gOn l : gRPC Loms.OrderPay\n\t- orderID +activate l +l -> ss : stocks.ReserveRemove() +l -> os : order.SetStatus(payed) + +l -> u : Response: OK (code_id=0) + +deactivate l + +@enduml diff --git a/docs/homework-3/img/loms-order-pay.png b/docs/homework-3/img/loms-order-pay.png new file mode 100644 index 0000000000000000000000000000000000000000..d30bda2a0c3d58b553731275d37472ff8c9d2412 GIT binary patch literal 21290 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 IbxsLQ07D5NeE l : gRPC Loms.StocksInfo\n\t- sku +activate l + +l -> ss : stocks.GetBySKU() + +l -> u : Response: OK (code_id=0)\n- count +deactivate l + +@enduml diff --git a/docs/homework-3/img/loms-stok-info.png b/docs/homework-3/img/loms-stok-info.png new file mode 100644 index 0000000000000000000000000000000000000000..6764b89a56c844880007bfef3c9ddafea9ebd59c GIT binary patch literal 16914 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 ar7;sI`Z} 0 { + mmOrderCreate.mock.t.Fatalf("Some expectations are already set for the OrderRepository.OrderCreate method") + } + + mmOrderCreate.mock.funcOrderCreate = f + mmOrderCreate.mock.funcOrderCreateOrigin = minimock.CallerInfo(1) + return mmOrderCreate.mock +} + +// When sets expectation for the OrderRepository.OrderCreate which will trigger the result defined by the following +// Then helper +func (mmOrderCreate *mOrderRepositoryMockOrderCreate) When(ctx context.Context, order *entity.Order) *OrderRepositoryMockOrderCreateExpectation { + if mmOrderCreate.mock.funcOrderCreate != nil { + mmOrderCreate.mock.t.Fatalf("OrderRepositoryMock.OrderCreate mock is already set by Set") + } + + expectation := &OrderRepositoryMockOrderCreateExpectation{ + mock: mmOrderCreate.mock, + params: &OrderRepositoryMockOrderCreateParams{ctx, order}, + expectationOrigins: OrderRepositoryMockOrderCreateExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmOrderCreate.expectations = append(mmOrderCreate.expectations, expectation) + return expectation +} + +// Then sets up OrderRepository.OrderCreate return parameters for the expectation previously defined by the When method +func (e *OrderRepositoryMockOrderCreateExpectation) Then(i1 entity.ID, err error) *OrderRepositoryMock { + e.results = &OrderRepositoryMockOrderCreateResults{i1, err} + return e.mock +} + +// Times sets number of times OrderRepository.OrderCreate should be invoked +func (mmOrderCreate *mOrderRepositoryMockOrderCreate) Times(n uint64) *mOrderRepositoryMockOrderCreate { + if n == 0 { + mmOrderCreate.mock.t.Fatalf("Times of OrderRepositoryMock.OrderCreate mock can not be zero") + } + mm_atomic.StoreUint64(&mmOrderCreate.expectedInvocations, n) + mmOrderCreate.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmOrderCreate +} + +func (mmOrderCreate *mOrderRepositoryMockOrderCreate) invocationsDone() bool { + if len(mmOrderCreate.expectations) == 0 && mmOrderCreate.defaultExpectation == nil && mmOrderCreate.mock.funcOrderCreate == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmOrderCreate.mock.afterOrderCreateCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmOrderCreate.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// OrderCreate implements mm_service.OrderRepository +func (mmOrderCreate *OrderRepositoryMock) OrderCreate(ctx context.Context, order *entity.Order) (i1 entity.ID, err error) { + mm_atomic.AddUint64(&mmOrderCreate.beforeOrderCreateCounter, 1) + defer mm_atomic.AddUint64(&mmOrderCreate.afterOrderCreateCounter, 1) + + mmOrderCreate.t.Helper() + + if mmOrderCreate.inspectFuncOrderCreate != nil { + mmOrderCreate.inspectFuncOrderCreate(ctx, order) + } + + mm_params := OrderRepositoryMockOrderCreateParams{ctx, order} + + // Record call args + mmOrderCreate.OrderCreateMock.mutex.Lock() + mmOrderCreate.OrderCreateMock.callArgs = append(mmOrderCreate.OrderCreateMock.callArgs, &mm_params) + mmOrderCreate.OrderCreateMock.mutex.Unlock() + + for _, e := range mmOrderCreate.OrderCreateMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.i1, e.results.err + } + } + + if mmOrderCreate.OrderCreateMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmOrderCreate.OrderCreateMock.defaultExpectation.Counter, 1) + mm_want := mmOrderCreate.OrderCreateMock.defaultExpectation.params + mm_want_ptrs := mmOrderCreate.OrderCreateMock.defaultExpectation.paramPtrs + + mm_got := OrderRepositoryMockOrderCreateParams{ctx, order} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmOrderCreate.t.Errorf("OrderRepositoryMock.OrderCreate got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderCreate.OrderCreateMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.order != nil && !minimock.Equal(*mm_want_ptrs.order, mm_got.order) { + mmOrderCreate.t.Errorf("OrderRepositoryMock.OrderCreate got unexpected parameter order, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderCreate.OrderCreateMock.defaultExpectation.expectationOrigins.originOrder, *mm_want_ptrs.order, mm_got.order, minimock.Diff(*mm_want_ptrs.order, mm_got.order)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmOrderCreate.t.Errorf("OrderRepositoryMock.OrderCreate got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderCreate.OrderCreateMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmOrderCreate.OrderCreateMock.defaultExpectation.results + if mm_results == nil { + mmOrderCreate.t.Fatal("No results are set for the OrderRepositoryMock.OrderCreate") + } + return (*mm_results).i1, (*mm_results).err + } + if mmOrderCreate.funcOrderCreate != nil { + return mmOrderCreate.funcOrderCreate(ctx, order) + } + mmOrderCreate.t.Fatalf("Unexpected call to OrderRepositoryMock.OrderCreate. %v %v", ctx, order) + return +} + +// OrderCreateAfterCounter returns a count of finished OrderRepositoryMock.OrderCreate invocations +func (mmOrderCreate *OrderRepositoryMock) OrderCreateAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderCreate.afterOrderCreateCounter) +} + +// OrderCreateBeforeCounter returns a count of OrderRepositoryMock.OrderCreate invocations +func (mmOrderCreate *OrderRepositoryMock) OrderCreateBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderCreate.beforeOrderCreateCounter) +} + +// Calls returns a list of arguments used in each call to OrderRepositoryMock.OrderCreate. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmOrderCreate *mOrderRepositoryMockOrderCreate) Calls() []*OrderRepositoryMockOrderCreateParams { + mmOrderCreate.mutex.RLock() + + argCopy := make([]*OrderRepositoryMockOrderCreateParams, len(mmOrderCreate.callArgs)) + copy(argCopy, mmOrderCreate.callArgs) + + mmOrderCreate.mutex.RUnlock() + + return argCopy +} + +// MinimockOrderCreateDone returns true if the count of the OrderCreate invocations corresponds +// the number of defined expectations +func (m *OrderRepositoryMock) MinimockOrderCreateDone() bool { + if m.OrderCreateMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.OrderCreateMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.OrderCreateMock.invocationsDone() +} + +// MinimockOrderCreateInspect logs each unmet expectation +func (m *OrderRepositoryMock) MinimockOrderCreateInspect() { + for _, e := range m.OrderCreateMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderCreate at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterOrderCreateCounter := mm_atomic.LoadUint64(&m.afterOrderCreateCounter) + // if default expectation was set then invocations count should be greater than zero + if m.OrderCreateMock.defaultExpectation != nil && afterOrderCreateCounter < 1 { + if m.OrderCreateMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderCreate at\n%s", m.OrderCreateMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderCreate at\n%s with params: %#v", m.OrderCreateMock.defaultExpectation.expectationOrigins.origin, *m.OrderCreateMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcOrderCreate != nil && afterOrderCreateCounter < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderCreate at\n%s", m.funcOrderCreateOrigin) + } + + if !m.OrderCreateMock.invocationsDone() && afterOrderCreateCounter > 0 { + m.t.Errorf("Expected %d calls to OrderRepositoryMock.OrderCreate at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.OrderCreateMock.expectedInvocations), m.OrderCreateMock.expectedInvocationsOrigin, afterOrderCreateCounter) + } +} + +type mOrderRepositoryMockOrderGetByID struct { + optional bool + mock *OrderRepositoryMock + defaultExpectation *OrderRepositoryMockOrderGetByIDExpectation + expectations []*OrderRepositoryMockOrderGetByIDExpectation + + callArgs []*OrderRepositoryMockOrderGetByIDParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// OrderRepositoryMockOrderGetByIDExpectation specifies expectation struct of the OrderRepository.OrderGetByID +type OrderRepositoryMockOrderGetByIDExpectation struct { + mock *OrderRepositoryMock + params *OrderRepositoryMockOrderGetByIDParams + paramPtrs *OrderRepositoryMockOrderGetByIDParamPtrs + expectationOrigins OrderRepositoryMockOrderGetByIDExpectationOrigins + results *OrderRepositoryMockOrderGetByIDResults + returnOrigin string + Counter uint64 +} + +// OrderRepositoryMockOrderGetByIDParams contains parameters of the OrderRepository.OrderGetByID +type OrderRepositoryMockOrderGetByIDParams struct { + ctx context.Context + orderID entity.ID +} + +// OrderRepositoryMockOrderGetByIDParamPtrs contains pointers to parameters of the OrderRepository.OrderGetByID +type OrderRepositoryMockOrderGetByIDParamPtrs struct { + ctx *context.Context + orderID *entity.ID +} + +// OrderRepositoryMockOrderGetByIDResults contains results of the OrderRepository.OrderGetByID +type OrderRepositoryMockOrderGetByIDResults struct { + op1 *entity.Order + err error +} + +// OrderRepositoryMockOrderGetByIDOrigins contains origins of expectations of the OrderRepository.OrderGetByID +type OrderRepositoryMockOrderGetByIDExpectationOrigins struct { + origin string + originCtx string + originOrderID string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Optional() *mOrderRepositoryMockOrderGetByID { + mmOrderGetByID.optional = true + return mmOrderGetByID +} + +// Expect sets up expected params for OrderRepository.OrderGetByID +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Expect(ctx context.Context, orderID entity.ID) *mOrderRepositoryMockOrderGetByID { + if mmOrderGetByID.mock.funcOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Set") + } + + if mmOrderGetByID.defaultExpectation == nil { + mmOrderGetByID.defaultExpectation = &OrderRepositoryMockOrderGetByIDExpectation{} + } + + if mmOrderGetByID.defaultExpectation.paramPtrs != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by ExpectParams functions") + } + + mmOrderGetByID.defaultExpectation.params = &OrderRepositoryMockOrderGetByIDParams{ctx, orderID} + mmOrderGetByID.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmOrderGetByID.expectations { + if minimock.Equal(e.params, mmOrderGetByID.defaultExpectation.params) { + mmOrderGetByID.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmOrderGetByID.defaultExpectation.params) + } + } + + return mmOrderGetByID +} + +// ExpectCtxParam1 sets up expected param ctx for OrderRepository.OrderGetByID +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) ExpectCtxParam1(ctx context.Context) *mOrderRepositoryMockOrderGetByID { + if mmOrderGetByID.mock.funcOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Set") + } + + if mmOrderGetByID.defaultExpectation == nil { + mmOrderGetByID.defaultExpectation = &OrderRepositoryMockOrderGetByIDExpectation{} + } + + if mmOrderGetByID.defaultExpectation.params != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Expect") + } + + if mmOrderGetByID.defaultExpectation.paramPtrs == nil { + mmOrderGetByID.defaultExpectation.paramPtrs = &OrderRepositoryMockOrderGetByIDParamPtrs{} + } + mmOrderGetByID.defaultExpectation.paramPtrs.ctx = &ctx + mmOrderGetByID.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmOrderGetByID +} + +// ExpectOrderIDParam2 sets up expected param orderID for OrderRepository.OrderGetByID +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) ExpectOrderIDParam2(orderID entity.ID) *mOrderRepositoryMockOrderGetByID { + if mmOrderGetByID.mock.funcOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Set") + } + + if mmOrderGetByID.defaultExpectation == nil { + mmOrderGetByID.defaultExpectation = &OrderRepositoryMockOrderGetByIDExpectation{} + } + + if mmOrderGetByID.defaultExpectation.params != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Expect") + } + + if mmOrderGetByID.defaultExpectation.paramPtrs == nil { + mmOrderGetByID.defaultExpectation.paramPtrs = &OrderRepositoryMockOrderGetByIDParamPtrs{} + } + mmOrderGetByID.defaultExpectation.paramPtrs.orderID = &orderID + mmOrderGetByID.defaultExpectation.expectationOrigins.originOrderID = minimock.CallerInfo(1) + + return mmOrderGetByID +} + +// Inspect accepts an inspector function that has same arguments as the OrderRepository.OrderGetByID +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Inspect(f func(ctx context.Context, orderID entity.ID)) *mOrderRepositoryMockOrderGetByID { + if mmOrderGetByID.mock.inspectFuncOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("Inspect function is already set for OrderRepositoryMock.OrderGetByID") + } + + mmOrderGetByID.mock.inspectFuncOrderGetByID = f + + return mmOrderGetByID +} + +// Return sets up results that will be returned by OrderRepository.OrderGetByID +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Return(op1 *entity.Order, err error) *OrderRepositoryMock { + if mmOrderGetByID.mock.funcOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Set") + } + + if mmOrderGetByID.defaultExpectation == nil { + mmOrderGetByID.defaultExpectation = &OrderRepositoryMockOrderGetByIDExpectation{mock: mmOrderGetByID.mock} + } + mmOrderGetByID.defaultExpectation.results = &OrderRepositoryMockOrderGetByIDResults{op1, err} + mmOrderGetByID.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmOrderGetByID.mock +} + +// Set uses given function f to mock the OrderRepository.OrderGetByID method +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Set(f func(ctx context.Context, orderID entity.ID) (op1 *entity.Order, err error)) *OrderRepositoryMock { + if mmOrderGetByID.defaultExpectation != nil { + mmOrderGetByID.mock.t.Fatalf("Default expectation is already set for the OrderRepository.OrderGetByID method") + } + + if len(mmOrderGetByID.expectations) > 0 { + mmOrderGetByID.mock.t.Fatalf("Some expectations are already set for the OrderRepository.OrderGetByID method") + } + + mmOrderGetByID.mock.funcOrderGetByID = f + mmOrderGetByID.mock.funcOrderGetByIDOrigin = minimock.CallerInfo(1) + return mmOrderGetByID.mock +} + +// When sets expectation for the OrderRepository.OrderGetByID which will trigger the result defined by the following +// Then helper +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) When(ctx context.Context, orderID entity.ID) *OrderRepositoryMockOrderGetByIDExpectation { + if mmOrderGetByID.mock.funcOrderGetByID != nil { + mmOrderGetByID.mock.t.Fatalf("OrderRepositoryMock.OrderGetByID mock is already set by Set") + } + + expectation := &OrderRepositoryMockOrderGetByIDExpectation{ + mock: mmOrderGetByID.mock, + params: &OrderRepositoryMockOrderGetByIDParams{ctx, orderID}, + expectationOrigins: OrderRepositoryMockOrderGetByIDExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmOrderGetByID.expectations = append(mmOrderGetByID.expectations, expectation) + return expectation +} + +// Then sets up OrderRepository.OrderGetByID return parameters for the expectation previously defined by the When method +func (e *OrderRepositoryMockOrderGetByIDExpectation) Then(op1 *entity.Order, err error) *OrderRepositoryMock { + e.results = &OrderRepositoryMockOrderGetByIDResults{op1, err} + return e.mock +} + +// Times sets number of times OrderRepository.OrderGetByID should be invoked +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Times(n uint64) *mOrderRepositoryMockOrderGetByID { + if n == 0 { + mmOrderGetByID.mock.t.Fatalf("Times of OrderRepositoryMock.OrderGetByID mock can not be zero") + } + mm_atomic.StoreUint64(&mmOrderGetByID.expectedInvocations, n) + mmOrderGetByID.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmOrderGetByID +} + +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) invocationsDone() bool { + if len(mmOrderGetByID.expectations) == 0 && mmOrderGetByID.defaultExpectation == nil && mmOrderGetByID.mock.funcOrderGetByID == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmOrderGetByID.mock.afterOrderGetByIDCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmOrderGetByID.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// OrderGetByID implements mm_service.OrderRepository +func (mmOrderGetByID *OrderRepositoryMock) OrderGetByID(ctx context.Context, orderID entity.ID) (op1 *entity.Order, err error) { + mm_atomic.AddUint64(&mmOrderGetByID.beforeOrderGetByIDCounter, 1) + defer mm_atomic.AddUint64(&mmOrderGetByID.afterOrderGetByIDCounter, 1) + + mmOrderGetByID.t.Helper() + + if mmOrderGetByID.inspectFuncOrderGetByID != nil { + mmOrderGetByID.inspectFuncOrderGetByID(ctx, orderID) + } + + mm_params := OrderRepositoryMockOrderGetByIDParams{ctx, orderID} + + // Record call args + mmOrderGetByID.OrderGetByIDMock.mutex.Lock() + mmOrderGetByID.OrderGetByIDMock.callArgs = append(mmOrderGetByID.OrderGetByIDMock.callArgs, &mm_params) + mmOrderGetByID.OrderGetByIDMock.mutex.Unlock() + + for _, e := range mmOrderGetByID.OrderGetByIDMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.op1, e.results.err + } + } + + if mmOrderGetByID.OrderGetByIDMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmOrderGetByID.OrderGetByIDMock.defaultExpectation.Counter, 1) + mm_want := mmOrderGetByID.OrderGetByIDMock.defaultExpectation.params + mm_want_ptrs := mmOrderGetByID.OrderGetByIDMock.defaultExpectation.paramPtrs + + mm_got := OrderRepositoryMockOrderGetByIDParams{ctx, orderID} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmOrderGetByID.t.Errorf("OrderRepositoryMock.OrderGetByID got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderGetByID.OrderGetByIDMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.orderID != nil && !minimock.Equal(*mm_want_ptrs.orderID, mm_got.orderID) { + mmOrderGetByID.t.Errorf("OrderRepositoryMock.OrderGetByID got unexpected parameter orderID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderGetByID.OrderGetByIDMock.defaultExpectation.expectationOrigins.originOrderID, *mm_want_ptrs.orderID, mm_got.orderID, minimock.Diff(*mm_want_ptrs.orderID, mm_got.orderID)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmOrderGetByID.t.Errorf("OrderRepositoryMock.OrderGetByID got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderGetByID.OrderGetByIDMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmOrderGetByID.OrderGetByIDMock.defaultExpectation.results + if mm_results == nil { + mmOrderGetByID.t.Fatal("No results are set for the OrderRepositoryMock.OrderGetByID") + } + return (*mm_results).op1, (*mm_results).err + } + if mmOrderGetByID.funcOrderGetByID != nil { + return mmOrderGetByID.funcOrderGetByID(ctx, orderID) + } + mmOrderGetByID.t.Fatalf("Unexpected call to OrderRepositoryMock.OrderGetByID. %v %v", ctx, orderID) + return +} + +// OrderGetByIDAfterCounter returns a count of finished OrderRepositoryMock.OrderGetByID invocations +func (mmOrderGetByID *OrderRepositoryMock) OrderGetByIDAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderGetByID.afterOrderGetByIDCounter) +} + +// OrderGetByIDBeforeCounter returns a count of OrderRepositoryMock.OrderGetByID invocations +func (mmOrderGetByID *OrderRepositoryMock) OrderGetByIDBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderGetByID.beforeOrderGetByIDCounter) +} + +// Calls returns a list of arguments used in each call to OrderRepositoryMock.OrderGetByID. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmOrderGetByID *mOrderRepositoryMockOrderGetByID) Calls() []*OrderRepositoryMockOrderGetByIDParams { + mmOrderGetByID.mutex.RLock() + + argCopy := make([]*OrderRepositoryMockOrderGetByIDParams, len(mmOrderGetByID.callArgs)) + copy(argCopy, mmOrderGetByID.callArgs) + + mmOrderGetByID.mutex.RUnlock() + + return argCopy +} + +// MinimockOrderGetByIDDone returns true if the count of the OrderGetByID invocations corresponds +// the number of defined expectations +func (m *OrderRepositoryMock) MinimockOrderGetByIDDone() bool { + if m.OrderGetByIDMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.OrderGetByIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.OrderGetByIDMock.invocationsDone() +} + +// MinimockOrderGetByIDInspect logs each unmet expectation +func (m *OrderRepositoryMock) MinimockOrderGetByIDInspect() { + for _, e := range m.OrderGetByIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderGetByID at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterOrderGetByIDCounter := mm_atomic.LoadUint64(&m.afterOrderGetByIDCounter) + // if default expectation was set then invocations count should be greater than zero + if m.OrderGetByIDMock.defaultExpectation != nil && afterOrderGetByIDCounter < 1 { + if m.OrderGetByIDMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderGetByID at\n%s", m.OrderGetByIDMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderGetByID at\n%s with params: %#v", m.OrderGetByIDMock.defaultExpectation.expectationOrigins.origin, *m.OrderGetByIDMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcOrderGetByID != nil && afterOrderGetByIDCounter < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderGetByID at\n%s", m.funcOrderGetByIDOrigin) + } + + if !m.OrderGetByIDMock.invocationsDone() && afterOrderGetByIDCounter > 0 { + m.t.Errorf("Expected %d calls to OrderRepositoryMock.OrderGetByID at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.OrderGetByIDMock.expectedInvocations), m.OrderGetByIDMock.expectedInvocationsOrigin, afterOrderGetByIDCounter) + } +} + +type mOrderRepositoryMockOrderSetStatus struct { + optional bool + mock *OrderRepositoryMock + defaultExpectation *OrderRepositoryMockOrderSetStatusExpectation + expectations []*OrderRepositoryMockOrderSetStatusExpectation + + callArgs []*OrderRepositoryMockOrderSetStatusParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// OrderRepositoryMockOrderSetStatusExpectation specifies expectation struct of the OrderRepository.OrderSetStatus +type OrderRepositoryMockOrderSetStatusExpectation struct { + mock *OrderRepositoryMock + params *OrderRepositoryMockOrderSetStatusParams + paramPtrs *OrderRepositoryMockOrderSetStatusParamPtrs + expectationOrigins OrderRepositoryMockOrderSetStatusExpectationOrigins + results *OrderRepositoryMockOrderSetStatusResults + returnOrigin string + Counter uint64 +} + +// OrderRepositoryMockOrderSetStatusParams contains parameters of the OrderRepository.OrderSetStatus +type OrderRepositoryMockOrderSetStatusParams struct { + ctx context.Context + orderID entity.ID + newStatus string +} + +// OrderRepositoryMockOrderSetStatusParamPtrs contains pointers to parameters of the OrderRepository.OrderSetStatus +type OrderRepositoryMockOrderSetStatusParamPtrs struct { + ctx *context.Context + orderID *entity.ID + newStatus *string +} + +// OrderRepositoryMockOrderSetStatusResults contains results of the OrderRepository.OrderSetStatus +type OrderRepositoryMockOrderSetStatusResults struct { + err error +} + +// OrderRepositoryMockOrderSetStatusOrigins contains origins of expectations of the OrderRepository.OrderSetStatus +type OrderRepositoryMockOrderSetStatusExpectationOrigins struct { + origin string + originCtx string + originOrderID string + originNewStatus string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Optional() *mOrderRepositoryMockOrderSetStatus { + mmOrderSetStatus.optional = true + return mmOrderSetStatus +} + +// Expect sets up expected params for OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Expect(ctx context.Context, orderID entity.ID, newStatus string) *mOrderRepositoryMockOrderSetStatus { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + if mmOrderSetStatus.defaultExpectation == nil { + mmOrderSetStatus.defaultExpectation = &OrderRepositoryMockOrderSetStatusExpectation{} + } + + if mmOrderSetStatus.defaultExpectation.paramPtrs != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by ExpectParams functions") + } + + mmOrderSetStatus.defaultExpectation.params = &OrderRepositoryMockOrderSetStatusParams{ctx, orderID, newStatus} + mmOrderSetStatus.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmOrderSetStatus.expectations { + if minimock.Equal(e.params, mmOrderSetStatus.defaultExpectation.params) { + mmOrderSetStatus.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmOrderSetStatus.defaultExpectation.params) + } + } + + return mmOrderSetStatus +} + +// ExpectCtxParam1 sets up expected param ctx for OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) ExpectCtxParam1(ctx context.Context) *mOrderRepositoryMockOrderSetStatus { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + if mmOrderSetStatus.defaultExpectation == nil { + mmOrderSetStatus.defaultExpectation = &OrderRepositoryMockOrderSetStatusExpectation{} + } + + if mmOrderSetStatus.defaultExpectation.params != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Expect") + } + + if mmOrderSetStatus.defaultExpectation.paramPtrs == nil { + mmOrderSetStatus.defaultExpectation.paramPtrs = &OrderRepositoryMockOrderSetStatusParamPtrs{} + } + mmOrderSetStatus.defaultExpectation.paramPtrs.ctx = &ctx + mmOrderSetStatus.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmOrderSetStatus +} + +// ExpectOrderIDParam2 sets up expected param orderID for OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) ExpectOrderIDParam2(orderID entity.ID) *mOrderRepositoryMockOrderSetStatus { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + if mmOrderSetStatus.defaultExpectation == nil { + mmOrderSetStatus.defaultExpectation = &OrderRepositoryMockOrderSetStatusExpectation{} + } + + if mmOrderSetStatus.defaultExpectation.params != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Expect") + } + + if mmOrderSetStatus.defaultExpectation.paramPtrs == nil { + mmOrderSetStatus.defaultExpectation.paramPtrs = &OrderRepositoryMockOrderSetStatusParamPtrs{} + } + mmOrderSetStatus.defaultExpectation.paramPtrs.orderID = &orderID + mmOrderSetStatus.defaultExpectation.expectationOrigins.originOrderID = minimock.CallerInfo(1) + + return mmOrderSetStatus +} + +// ExpectNewStatusParam3 sets up expected param newStatus for OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) ExpectNewStatusParam3(newStatus string) *mOrderRepositoryMockOrderSetStatus { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + if mmOrderSetStatus.defaultExpectation == nil { + mmOrderSetStatus.defaultExpectation = &OrderRepositoryMockOrderSetStatusExpectation{} + } + + if mmOrderSetStatus.defaultExpectation.params != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Expect") + } + + if mmOrderSetStatus.defaultExpectation.paramPtrs == nil { + mmOrderSetStatus.defaultExpectation.paramPtrs = &OrderRepositoryMockOrderSetStatusParamPtrs{} + } + mmOrderSetStatus.defaultExpectation.paramPtrs.newStatus = &newStatus + mmOrderSetStatus.defaultExpectation.expectationOrigins.originNewStatus = minimock.CallerInfo(1) + + return mmOrderSetStatus +} + +// Inspect accepts an inspector function that has same arguments as the OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Inspect(f func(ctx context.Context, orderID entity.ID, newStatus string)) *mOrderRepositoryMockOrderSetStatus { + if mmOrderSetStatus.mock.inspectFuncOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("Inspect function is already set for OrderRepositoryMock.OrderSetStatus") + } + + mmOrderSetStatus.mock.inspectFuncOrderSetStatus = f + + return mmOrderSetStatus +} + +// Return sets up results that will be returned by OrderRepository.OrderSetStatus +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Return(err error) *OrderRepositoryMock { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + if mmOrderSetStatus.defaultExpectation == nil { + mmOrderSetStatus.defaultExpectation = &OrderRepositoryMockOrderSetStatusExpectation{mock: mmOrderSetStatus.mock} + } + mmOrderSetStatus.defaultExpectation.results = &OrderRepositoryMockOrderSetStatusResults{err} + mmOrderSetStatus.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmOrderSetStatus.mock +} + +// Set uses given function f to mock the OrderRepository.OrderSetStatus method +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Set(f func(ctx context.Context, orderID entity.ID, newStatus string) (err error)) *OrderRepositoryMock { + if mmOrderSetStatus.defaultExpectation != nil { + mmOrderSetStatus.mock.t.Fatalf("Default expectation is already set for the OrderRepository.OrderSetStatus method") + } + + if len(mmOrderSetStatus.expectations) > 0 { + mmOrderSetStatus.mock.t.Fatalf("Some expectations are already set for the OrderRepository.OrderSetStatus method") + } + + mmOrderSetStatus.mock.funcOrderSetStatus = f + mmOrderSetStatus.mock.funcOrderSetStatusOrigin = minimock.CallerInfo(1) + return mmOrderSetStatus.mock +} + +// When sets expectation for the OrderRepository.OrderSetStatus which will trigger the result defined by the following +// Then helper +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) When(ctx context.Context, orderID entity.ID, newStatus string) *OrderRepositoryMockOrderSetStatusExpectation { + if mmOrderSetStatus.mock.funcOrderSetStatus != nil { + mmOrderSetStatus.mock.t.Fatalf("OrderRepositoryMock.OrderSetStatus mock is already set by Set") + } + + expectation := &OrderRepositoryMockOrderSetStatusExpectation{ + mock: mmOrderSetStatus.mock, + params: &OrderRepositoryMockOrderSetStatusParams{ctx, orderID, newStatus}, + expectationOrigins: OrderRepositoryMockOrderSetStatusExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmOrderSetStatus.expectations = append(mmOrderSetStatus.expectations, expectation) + return expectation +} + +// Then sets up OrderRepository.OrderSetStatus return parameters for the expectation previously defined by the When method +func (e *OrderRepositoryMockOrderSetStatusExpectation) Then(err error) *OrderRepositoryMock { + e.results = &OrderRepositoryMockOrderSetStatusResults{err} + return e.mock +} + +// Times sets number of times OrderRepository.OrderSetStatus should be invoked +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Times(n uint64) *mOrderRepositoryMockOrderSetStatus { + if n == 0 { + mmOrderSetStatus.mock.t.Fatalf("Times of OrderRepositoryMock.OrderSetStatus mock can not be zero") + } + mm_atomic.StoreUint64(&mmOrderSetStatus.expectedInvocations, n) + mmOrderSetStatus.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmOrderSetStatus +} + +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) invocationsDone() bool { + if len(mmOrderSetStatus.expectations) == 0 && mmOrderSetStatus.defaultExpectation == nil && mmOrderSetStatus.mock.funcOrderSetStatus == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmOrderSetStatus.mock.afterOrderSetStatusCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmOrderSetStatus.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// OrderSetStatus implements mm_service.OrderRepository +func (mmOrderSetStatus *OrderRepositoryMock) OrderSetStatus(ctx context.Context, orderID entity.ID, newStatus string) (err error) { + mm_atomic.AddUint64(&mmOrderSetStatus.beforeOrderSetStatusCounter, 1) + defer mm_atomic.AddUint64(&mmOrderSetStatus.afterOrderSetStatusCounter, 1) + + mmOrderSetStatus.t.Helper() + + if mmOrderSetStatus.inspectFuncOrderSetStatus != nil { + mmOrderSetStatus.inspectFuncOrderSetStatus(ctx, orderID, newStatus) + } + + mm_params := OrderRepositoryMockOrderSetStatusParams{ctx, orderID, newStatus} + + // Record call args + mmOrderSetStatus.OrderSetStatusMock.mutex.Lock() + mmOrderSetStatus.OrderSetStatusMock.callArgs = append(mmOrderSetStatus.OrderSetStatusMock.callArgs, &mm_params) + mmOrderSetStatus.OrderSetStatusMock.mutex.Unlock() + + for _, e := range mmOrderSetStatus.OrderSetStatusMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmOrderSetStatus.OrderSetStatusMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.Counter, 1) + mm_want := mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.params + mm_want_ptrs := mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.paramPtrs + + mm_got := OrderRepositoryMockOrderSetStatusParams{ctx, orderID, newStatus} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmOrderSetStatus.t.Errorf("OrderRepositoryMock.OrderSetStatus got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.orderID != nil && !minimock.Equal(*mm_want_ptrs.orderID, mm_got.orderID) { + mmOrderSetStatus.t.Errorf("OrderRepositoryMock.OrderSetStatus got unexpected parameter orderID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.expectationOrigins.originOrderID, *mm_want_ptrs.orderID, mm_got.orderID, minimock.Diff(*mm_want_ptrs.orderID, mm_got.orderID)) + } + + if mm_want_ptrs.newStatus != nil && !minimock.Equal(*mm_want_ptrs.newStatus, mm_got.newStatus) { + mmOrderSetStatus.t.Errorf("OrderRepositoryMock.OrderSetStatus got unexpected parameter newStatus, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.expectationOrigins.originNewStatus, *mm_want_ptrs.newStatus, mm_got.newStatus, minimock.Diff(*mm_want_ptrs.newStatus, mm_got.newStatus)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmOrderSetStatus.t.Errorf("OrderRepositoryMock.OrderSetStatus got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmOrderSetStatus.OrderSetStatusMock.defaultExpectation.results + if mm_results == nil { + mmOrderSetStatus.t.Fatal("No results are set for the OrderRepositoryMock.OrderSetStatus") + } + return (*mm_results).err + } + if mmOrderSetStatus.funcOrderSetStatus != nil { + return mmOrderSetStatus.funcOrderSetStatus(ctx, orderID, newStatus) + } + mmOrderSetStatus.t.Fatalf("Unexpected call to OrderRepositoryMock.OrderSetStatus. %v %v %v", ctx, orderID, newStatus) + return +} + +// OrderSetStatusAfterCounter returns a count of finished OrderRepositoryMock.OrderSetStatus invocations +func (mmOrderSetStatus *OrderRepositoryMock) OrderSetStatusAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderSetStatus.afterOrderSetStatusCounter) +} + +// OrderSetStatusBeforeCounter returns a count of OrderRepositoryMock.OrderSetStatus invocations +func (mmOrderSetStatus *OrderRepositoryMock) OrderSetStatusBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmOrderSetStatus.beforeOrderSetStatusCounter) +} + +// Calls returns a list of arguments used in each call to OrderRepositoryMock.OrderSetStatus. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmOrderSetStatus *mOrderRepositoryMockOrderSetStatus) Calls() []*OrderRepositoryMockOrderSetStatusParams { + mmOrderSetStatus.mutex.RLock() + + argCopy := make([]*OrderRepositoryMockOrderSetStatusParams, len(mmOrderSetStatus.callArgs)) + copy(argCopy, mmOrderSetStatus.callArgs) + + mmOrderSetStatus.mutex.RUnlock() + + return argCopy +} + +// MinimockOrderSetStatusDone returns true if the count of the OrderSetStatus invocations corresponds +// the number of defined expectations +func (m *OrderRepositoryMock) MinimockOrderSetStatusDone() bool { + if m.OrderSetStatusMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.OrderSetStatusMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.OrderSetStatusMock.invocationsDone() +} + +// MinimockOrderSetStatusInspect logs each unmet expectation +func (m *OrderRepositoryMock) MinimockOrderSetStatusInspect() { + for _, e := range m.OrderSetStatusMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderSetStatus at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterOrderSetStatusCounter := mm_atomic.LoadUint64(&m.afterOrderSetStatusCounter) + // if default expectation was set then invocations count should be greater than zero + if m.OrderSetStatusMock.defaultExpectation != nil && afterOrderSetStatusCounter < 1 { + if m.OrderSetStatusMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderSetStatus at\n%s", m.OrderSetStatusMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderSetStatus at\n%s with params: %#v", m.OrderSetStatusMock.defaultExpectation.expectationOrigins.origin, *m.OrderSetStatusMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcOrderSetStatus != nil && afterOrderSetStatusCounter < 1 { + m.t.Errorf("Expected call to OrderRepositoryMock.OrderSetStatus at\n%s", m.funcOrderSetStatusOrigin) + } + + if !m.OrderSetStatusMock.invocationsDone() && afterOrderSetStatusCounter > 0 { + m.t.Errorf("Expected %d calls to OrderRepositoryMock.OrderSetStatus at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.OrderSetStatusMock.expectedInvocations), m.OrderSetStatusMock.expectedInvocationsOrigin, afterOrderSetStatusCounter) + } +} + +// MinimockFinish checks that all mocked methods have been called the expected number of times +func (m *OrderRepositoryMock) MinimockFinish() { + m.finishOnce.Do(func() { + if !m.minimockDone() { + m.MinimockOrderCreateInspect() + + m.MinimockOrderGetByIDInspect() + + m.MinimockOrderSetStatusInspect() + } + }) +} + +// MinimockWait waits for all mocked methods to be called the expected number of times +func (m *OrderRepositoryMock) MinimockWait(timeout mm_time.Duration) { + timeoutCh := mm_time.After(timeout) + for { + if m.minimockDone() { + return + } + select { + case <-timeoutCh: + m.MinimockFinish() + return + case <-mm_time.After(10 * mm_time.Millisecond): + } + } +} + +func (m *OrderRepositoryMock) minimockDone() bool { + done := true + return done && + m.MinimockOrderCreateDone() && + m.MinimockOrderGetByIDDone() && + m.MinimockOrderSetStatusDone() +} diff --git a/loms/internal/domain/service/mock/stock_repository_mock.go b/loms/internal/domain/service/mock/stock_repository_mock.go new file mode 100644 index 0000000..62f73b4 --- /dev/null +++ b/loms/internal/domain/service/mock/stock_repository_mock.go @@ -0,0 +1,1483 @@ +// Code generated by http://github.com/gojuno/minimock (v3.4.5). DO NOT EDIT. + +package mock + +//go:generate minimock -i route256/loms/internal/domain/loms/service.StockRepository -o stock_repository_mock.go -n StockRepositoryMock -p mock + +import ( + "context" + "route256/loms/internal/domain/entity" + "sync" + mm_atomic "sync/atomic" + mm_time "time" + + "github.com/gojuno/minimock/v3" +) + +// StockRepositoryMock implements mm_service.StockRepository +type StockRepositoryMock struct { + t minimock.Tester + finishOnce sync.Once + + funcStockCancel func(ctx context.Context, stock *entity.Stock) (err error) + funcStockCancelOrigin string + inspectFuncStockCancel func(ctx context.Context, stock *entity.Stock) + afterStockCancelCounter uint64 + beforeStockCancelCounter uint64 + StockCancelMock mStockRepositoryMockStockCancel + + funcStockGetByID func(ctx context.Context, sku entity.Sku) (sp1 *entity.Stock, err error) + funcStockGetByIDOrigin string + inspectFuncStockGetByID func(ctx context.Context, sku entity.Sku) + afterStockGetByIDCounter uint64 + beforeStockGetByIDCounter uint64 + StockGetByIDMock mStockRepositoryMockStockGetByID + + funcStockReserve func(ctx context.Context, stock *entity.Stock) (err error) + funcStockReserveOrigin string + inspectFuncStockReserve func(ctx context.Context, stock *entity.Stock) + afterStockReserveCounter uint64 + beforeStockReserveCounter uint64 + StockReserveMock mStockRepositoryMockStockReserve + + funcStockReserveRemove func(ctx context.Context, stock *entity.Stock) (err error) + funcStockReserveRemoveOrigin string + inspectFuncStockReserveRemove func(ctx context.Context, stock *entity.Stock) + afterStockReserveRemoveCounter uint64 + beforeStockReserveRemoveCounter uint64 + StockReserveRemoveMock mStockRepositoryMockStockReserveRemove +} + +// NewStockRepositoryMock returns a mock for mm_service.StockRepository +func NewStockRepositoryMock(t minimock.Tester) *StockRepositoryMock { + m := &StockRepositoryMock{t: t} + + if controller, ok := t.(minimock.MockController); ok { + controller.RegisterMocker(m) + } + + m.StockCancelMock = mStockRepositoryMockStockCancel{mock: m} + m.StockCancelMock.callArgs = []*StockRepositoryMockStockCancelParams{} + + m.StockGetByIDMock = mStockRepositoryMockStockGetByID{mock: m} + m.StockGetByIDMock.callArgs = []*StockRepositoryMockStockGetByIDParams{} + + m.StockReserveMock = mStockRepositoryMockStockReserve{mock: m} + m.StockReserveMock.callArgs = []*StockRepositoryMockStockReserveParams{} + + m.StockReserveRemoveMock = mStockRepositoryMockStockReserveRemove{mock: m} + m.StockReserveRemoveMock.callArgs = []*StockRepositoryMockStockReserveRemoveParams{} + + t.Cleanup(m.MinimockFinish) + + return m +} + +type mStockRepositoryMockStockCancel struct { + optional bool + mock *StockRepositoryMock + defaultExpectation *StockRepositoryMockStockCancelExpectation + expectations []*StockRepositoryMockStockCancelExpectation + + callArgs []*StockRepositoryMockStockCancelParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// StockRepositoryMockStockCancelExpectation specifies expectation struct of the StockRepository.StockCancel +type StockRepositoryMockStockCancelExpectation struct { + mock *StockRepositoryMock + params *StockRepositoryMockStockCancelParams + paramPtrs *StockRepositoryMockStockCancelParamPtrs + expectationOrigins StockRepositoryMockStockCancelExpectationOrigins + results *StockRepositoryMockStockCancelResults + returnOrigin string + Counter uint64 +} + +// StockRepositoryMockStockCancelParams contains parameters of the StockRepository.StockCancel +type StockRepositoryMockStockCancelParams struct { + ctx context.Context + stock *entity.Stock +} + +// StockRepositoryMockStockCancelParamPtrs contains pointers to parameters of the StockRepository.StockCancel +type StockRepositoryMockStockCancelParamPtrs struct { + ctx *context.Context + stock **entity.Stock +} + +// StockRepositoryMockStockCancelResults contains results of the StockRepository.StockCancel +type StockRepositoryMockStockCancelResults struct { + err error +} + +// StockRepositoryMockStockCancelOrigins contains origins of expectations of the StockRepository.StockCancel +type StockRepositoryMockStockCancelExpectationOrigins struct { + origin string + originCtx string + originStock string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmStockCancel *mStockRepositoryMockStockCancel) Optional() *mStockRepositoryMockStockCancel { + mmStockCancel.optional = true + return mmStockCancel +} + +// Expect sets up expected params for StockRepository.StockCancel +func (mmStockCancel *mStockRepositoryMockStockCancel) Expect(ctx context.Context, stock *entity.Stock) *mStockRepositoryMockStockCancel { + if mmStockCancel.mock.funcStockCancel != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Set") + } + + if mmStockCancel.defaultExpectation == nil { + mmStockCancel.defaultExpectation = &StockRepositoryMockStockCancelExpectation{} + } + + if mmStockCancel.defaultExpectation.paramPtrs != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by ExpectParams functions") + } + + mmStockCancel.defaultExpectation.params = &StockRepositoryMockStockCancelParams{ctx, stock} + mmStockCancel.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmStockCancel.expectations { + if minimock.Equal(e.params, mmStockCancel.defaultExpectation.params) { + mmStockCancel.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmStockCancel.defaultExpectation.params) + } + } + + return mmStockCancel +} + +// ExpectCtxParam1 sets up expected param ctx for StockRepository.StockCancel +func (mmStockCancel *mStockRepositoryMockStockCancel) ExpectCtxParam1(ctx context.Context) *mStockRepositoryMockStockCancel { + if mmStockCancel.mock.funcStockCancel != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Set") + } + + if mmStockCancel.defaultExpectation == nil { + mmStockCancel.defaultExpectation = &StockRepositoryMockStockCancelExpectation{} + } + + if mmStockCancel.defaultExpectation.params != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Expect") + } + + if mmStockCancel.defaultExpectation.paramPtrs == nil { + mmStockCancel.defaultExpectation.paramPtrs = &StockRepositoryMockStockCancelParamPtrs{} + } + mmStockCancel.defaultExpectation.paramPtrs.ctx = &ctx + mmStockCancel.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmStockCancel +} + +// ExpectStockParam2 sets up expected param stock for StockRepository.StockCancel +func (mmStockCancel *mStockRepositoryMockStockCancel) ExpectStockParam2(stock *entity.Stock) *mStockRepositoryMockStockCancel { + if mmStockCancel.mock.funcStockCancel != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Set") + } + + if mmStockCancel.defaultExpectation == nil { + mmStockCancel.defaultExpectation = &StockRepositoryMockStockCancelExpectation{} + } + + if mmStockCancel.defaultExpectation.params != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Expect") + } + + if mmStockCancel.defaultExpectation.paramPtrs == nil { + mmStockCancel.defaultExpectation.paramPtrs = &StockRepositoryMockStockCancelParamPtrs{} + } + mmStockCancel.defaultExpectation.paramPtrs.stock = &stock + mmStockCancel.defaultExpectation.expectationOrigins.originStock = minimock.CallerInfo(1) + + return mmStockCancel +} + +// Inspect accepts an inspector function that has same arguments as the StockRepository.StockCancel +func (mmStockCancel *mStockRepositoryMockStockCancel) Inspect(f func(ctx context.Context, stock *entity.Stock)) *mStockRepositoryMockStockCancel { + if mmStockCancel.mock.inspectFuncStockCancel != nil { + mmStockCancel.mock.t.Fatalf("Inspect function is already set for StockRepositoryMock.StockCancel") + } + + mmStockCancel.mock.inspectFuncStockCancel = f + + return mmStockCancel +} + +// Return sets up results that will be returned by StockRepository.StockCancel +func (mmStockCancel *mStockRepositoryMockStockCancel) Return(err error) *StockRepositoryMock { + if mmStockCancel.mock.funcStockCancel != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Set") + } + + if mmStockCancel.defaultExpectation == nil { + mmStockCancel.defaultExpectation = &StockRepositoryMockStockCancelExpectation{mock: mmStockCancel.mock} + } + mmStockCancel.defaultExpectation.results = &StockRepositoryMockStockCancelResults{err} + mmStockCancel.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmStockCancel.mock +} + +// Set uses given function f to mock the StockRepository.StockCancel method +func (mmStockCancel *mStockRepositoryMockStockCancel) Set(f func(ctx context.Context, stock *entity.Stock) (err error)) *StockRepositoryMock { + if mmStockCancel.defaultExpectation != nil { + mmStockCancel.mock.t.Fatalf("Default expectation is already set for the StockRepository.StockCancel method") + } + + if len(mmStockCancel.expectations) > 0 { + mmStockCancel.mock.t.Fatalf("Some expectations are already set for the StockRepository.StockCancel method") + } + + mmStockCancel.mock.funcStockCancel = f + mmStockCancel.mock.funcStockCancelOrigin = minimock.CallerInfo(1) + return mmStockCancel.mock +} + +// When sets expectation for the StockRepository.StockCancel which will trigger the result defined by the following +// Then helper +func (mmStockCancel *mStockRepositoryMockStockCancel) When(ctx context.Context, stock *entity.Stock) *StockRepositoryMockStockCancelExpectation { + if mmStockCancel.mock.funcStockCancel != nil { + mmStockCancel.mock.t.Fatalf("StockRepositoryMock.StockCancel mock is already set by Set") + } + + expectation := &StockRepositoryMockStockCancelExpectation{ + mock: mmStockCancel.mock, + params: &StockRepositoryMockStockCancelParams{ctx, stock}, + expectationOrigins: StockRepositoryMockStockCancelExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmStockCancel.expectations = append(mmStockCancel.expectations, expectation) + return expectation +} + +// Then sets up StockRepository.StockCancel return parameters for the expectation previously defined by the When method +func (e *StockRepositoryMockStockCancelExpectation) Then(err error) *StockRepositoryMock { + e.results = &StockRepositoryMockStockCancelResults{err} + return e.mock +} + +// Times sets number of times StockRepository.StockCancel should be invoked +func (mmStockCancel *mStockRepositoryMockStockCancel) Times(n uint64) *mStockRepositoryMockStockCancel { + if n == 0 { + mmStockCancel.mock.t.Fatalf("Times of StockRepositoryMock.StockCancel mock can not be zero") + } + mm_atomic.StoreUint64(&mmStockCancel.expectedInvocations, n) + mmStockCancel.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmStockCancel +} + +func (mmStockCancel *mStockRepositoryMockStockCancel) invocationsDone() bool { + if len(mmStockCancel.expectations) == 0 && mmStockCancel.defaultExpectation == nil && mmStockCancel.mock.funcStockCancel == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmStockCancel.mock.afterStockCancelCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmStockCancel.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// StockCancel implements mm_service.StockRepository +func (mmStockCancel *StockRepositoryMock) StockCancel(ctx context.Context, stock *entity.Stock) (err error) { + mm_atomic.AddUint64(&mmStockCancel.beforeStockCancelCounter, 1) + defer mm_atomic.AddUint64(&mmStockCancel.afterStockCancelCounter, 1) + + mmStockCancel.t.Helper() + + if mmStockCancel.inspectFuncStockCancel != nil { + mmStockCancel.inspectFuncStockCancel(ctx, stock) + } + + mm_params := StockRepositoryMockStockCancelParams{ctx, stock} + + // Record call args + mmStockCancel.StockCancelMock.mutex.Lock() + mmStockCancel.StockCancelMock.callArgs = append(mmStockCancel.StockCancelMock.callArgs, &mm_params) + mmStockCancel.StockCancelMock.mutex.Unlock() + + for _, e := range mmStockCancel.StockCancelMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmStockCancel.StockCancelMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmStockCancel.StockCancelMock.defaultExpectation.Counter, 1) + mm_want := mmStockCancel.StockCancelMock.defaultExpectation.params + mm_want_ptrs := mmStockCancel.StockCancelMock.defaultExpectation.paramPtrs + + mm_got := StockRepositoryMockStockCancelParams{ctx, stock} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmStockCancel.t.Errorf("StockRepositoryMock.StockCancel got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockCancel.StockCancelMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.stock != nil && !minimock.Equal(*mm_want_ptrs.stock, mm_got.stock) { + mmStockCancel.t.Errorf("StockRepositoryMock.StockCancel got unexpected parameter stock, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockCancel.StockCancelMock.defaultExpectation.expectationOrigins.originStock, *mm_want_ptrs.stock, mm_got.stock, minimock.Diff(*mm_want_ptrs.stock, mm_got.stock)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmStockCancel.t.Errorf("StockRepositoryMock.StockCancel got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockCancel.StockCancelMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmStockCancel.StockCancelMock.defaultExpectation.results + if mm_results == nil { + mmStockCancel.t.Fatal("No results are set for the StockRepositoryMock.StockCancel") + } + return (*mm_results).err + } + if mmStockCancel.funcStockCancel != nil { + return mmStockCancel.funcStockCancel(ctx, stock) + } + mmStockCancel.t.Fatalf("Unexpected call to StockRepositoryMock.StockCancel. %v %v", ctx, stock) + return +} + +// StockCancelAfterCounter returns a count of finished StockRepositoryMock.StockCancel invocations +func (mmStockCancel *StockRepositoryMock) StockCancelAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockCancel.afterStockCancelCounter) +} + +// StockCancelBeforeCounter returns a count of StockRepositoryMock.StockCancel invocations +func (mmStockCancel *StockRepositoryMock) StockCancelBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockCancel.beforeStockCancelCounter) +} + +// Calls returns a list of arguments used in each call to StockRepositoryMock.StockCancel. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmStockCancel *mStockRepositoryMockStockCancel) Calls() []*StockRepositoryMockStockCancelParams { + mmStockCancel.mutex.RLock() + + argCopy := make([]*StockRepositoryMockStockCancelParams, len(mmStockCancel.callArgs)) + copy(argCopy, mmStockCancel.callArgs) + + mmStockCancel.mutex.RUnlock() + + return argCopy +} + +// MinimockStockCancelDone returns true if the count of the StockCancel invocations corresponds +// the number of defined expectations +func (m *StockRepositoryMock) MinimockStockCancelDone() bool { + if m.StockCancelMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.StockCancelMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.StockCancelMock.invocationsDone() +} + +// MinimockStockCancelInspect logs each unmet expectation +func (m *StockRepositoryMock) MinimockStockCancelInspect() { + for _, e := range m.StockCancelMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockCancel at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterStockCancelCounter := mm_atomic.LoadUint64(&m.afterStockCancelCounter) + // if default expectation was set then invocations count should be greater than zero + if m.StockCancelMock.defaultExpectation != nil && afterStockCancelCounter < 1 { + if m.StockCancelMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to StockRepositoryMock.StockCancel at\n%s", m.StockCancelMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to StockRepositoryMock.StockCancel at\n%s with params: %#v", m.StockCancelMock.defaultExpectation.expectationOrigins.origin, *m.StockCancelMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcStockCancel != nil && afterStockCancelCounter < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockCancel at\n%s", m.funcStockCancelOrigin) + } + + if !m.StockCancelMock.invocationsDone() && afterStockCancelCounter > 0 { + m.t.Errorf("Expected %d calls to StockRepositoryMock.StockCancel at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.StockCancelMock.expectedInvocations), m.StockCancelMock.expectedInvocationsOrigin, afterStockCancelCounter) + } +} + +type mStockRepositoryMockStockGetByID struct { + optional bool + mock *StockRepositoryMock + defaultExpectation *StockRepositoryMockStockGetByIDExpectation + expectations []*StockRepositoryMockStockGetByIDExpectation + + callArgs []*StockRepositoryMockStockGetByIDParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// StockRepositoryMockStockGetByIDExpectation specifies expectation struct of the StockRepository.StockGetByID +type StockRepositoryMockStockGetByIDExpectation struct { + mock *StockRepositoryMock + params *StockRepositoryMockStockGetByIDParams + paramPtrs *StockRepositoryMockStockGetByIDParamPtrs + expectationOrigins StockRepositoryMockStockGetByIDExpectationOrigins + results *StockRepositoryMockStockGetByIDResults + returnOrigin string + Counter uint64 +} + +// StockRepositoryMockStockGetByIDParams contains parameters of the StockRepository.StockGetByID +type StockRepositoryMockStockGetByIDParams struct { + ctx context.Context + sku entity.Sku +} + +// StockRepositoryMockStockGetByIDParamPtrs contains pointers to parameters of the StockRepository.StockGetByID +type StockRepositoryMockStockGetByIDParamPtrs struct { + ctx *context.Context + sku *entity.Sku +} + +// StockRepositoryMockStockGetByIDResults contains results of the StockRepository.StockGetByID +type StockRepositoryMockStockGetByIDResults struct { + sp1 *entity.Stock + err error +} + +// StockRepositoryMockStockGetByIDOrigins contains origins of expectations of the StockRepository.StockGetByID +type StockRepositoryMockStockGetByIDExpectationOrigins struct { + origin string + originCtx string + originSku string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Optional() *mStockRepositoryMockStockGetByID { + mmStockGetByID.optional = true + return mmStockGetByID +} + +// Expect sets up expected params for StockRepository.StockGetByID +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Expect(ctx context.Context, sku entity.Sku) *mStockRepositoryMockStockGetByID { + if mmStockGetByID.mock.funcStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Set") + } + + if mmStockGetByID.defaultExpectation == nil { + mmStockGetByID.defaultExpectation = &StockRepositoryMockStockGetByIDExpectation{} + } + + if mmStockGetByID.defaultExpectation.paramPtrs != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by ExpectParams functions") + } + + mmStockGetByID.defaultExpectation.params = &StockRepositoryMockStockGetByIDParams{ctx, sku} + mmStockGetByID.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmStockGetByID.expectations { + if minimock.Equal(e.params, mmStockGetByID.defaultExpectation.params) { + mmStockGetByID.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmStockGetByID.defaultExpectation.params) + } + } + + return mmStockGetByID +} + +// ExpectCtxParam1 sets up expected param ctx for StockRepository.StockGetByID +func (mmStockGetByID *mStockRepositoryMockStockGetByID) ExpectCtxParam1(ctx context.Context) *mStockRepositoryMockStockGetByID { + if mmStockGetByID.mock.funcStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Set") + } + + if mmStockGetByID.defaultExpectation == nil { + mmStockGetByID.defaultExpectation = &StockRepositoryMockStockGetByIDExpectation{} + } + + if mmStockGetByID.defaultExpectation.params != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Expect") + } + + if mmStockGetByID.defaultExpectation.paramPtrs == nil { + mmStockGetByID.defaultExpectation.paramPtrs = &StockRepositoryMockStockGetByIDParamPtrs{} + } + mmStockGetByID.defaultExpectation.paramPtrs.ctx = &ctx + mmStockGetByID.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmStockGetByID +} + +// ExpectSkuParam2 sets up expected param sku for StockRepository.StockGetByID +func (mmStockGetByID *mStockRepositoryMockStockGetByID) ExpectSkuParam2(sku entity.Sku) *mStockRepositoryMockStockGetByID { + if mmStockGetByID.mock.funcStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Set") + } + + if mmStockGetByID.defaultExpectation == nil { + mmStockGetByID.defaultExpectation = &StockRepositoryMockStockGetByIDExpectation{} + } + + if mmStockGetByID.defaultExpectation.params != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Expect") + } + + if mmStockGetByID.defaultExpectation.paramPtrs == nil { + mmStockGetByID.defaultExpectation.paramPtrs = &StockRepositoryMockStockGetByIDParamPtrs{} + } + mmStockGetByID.defaultExpectation.paramPtrs.sku = &sku + mmStockGetByID.defaultExpectation.expectationOrigins.originSku = minimock.CallerInfo(1) + + return mmStockGetByID +} + +// Inspect accepts an inspector function that has same arguments as the StockRepository.StockGetByID +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Inspect(f func(ctx context.Context, sku entity.Sku)) *mStockRepositoryMockStockGetByID { + if mmStockGetByID.mock.inspectFuncStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("Inspect function is already set for StockRepositoryMock.StockGetByID") + } + + mmStockGetByID.mock.inspectFuncStockGetByID = f + + return mmStockGetByID +} + +// Return sets up results that will be returned by StockRepository.StockGetByID +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Return(sp1 *entity.Stock, err error) *StockRepositoryMock { + if mmStockGetByID.mock.funcStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Set") + } + + if mmStockGetByID.defaultExpectation == nil { + mmStockGetByID.defaultExpectation = &StockRepositoryMockStockGetByIDExpectation{mock: mmStockGetByID.mock} + } + mmStockGetByID.defaultExpectation.results = &StockRepositoryMockStockGetByIDResults{sp1, err} + mmStockGetByID.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmStockGetByID.mock +} + +// Set uses given function f to mock the StockRepository.StockGetByID method +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Set(f func(ctx context.Context, sku entity.Sku) (sp1 *entity.Stock, err error)) *StockRepositoryMock { + if mmStockGetByID.defaultExpectation != nil { + mmStockGetByID.mock.t.Fatalf("Default expectation is already set for the StockRepository.StockGetByID method") + } + + if len(mmStockGetByID.expectations) > 0 { + mmStockGetByID.mock.t.Fatalf("Some expectations are already set for the StockRepository.StockGetByID method") + } + + mmStockGetByID.mock.funcStockGetByID = f + mmStockGetByID.mock.funcStockGetByIDOrigin = minimock.CallerInfo(1) + return mmStockGetByID.mock +} + +// When sets expectation for the StockRepository.StockGetByID which will trigger the result defined by the following +// Then helper +func (mmStockGetByID *mStockRepositoryMockStockGetByID) When(ctx context.Context, sku entity.Sku) *StockRepositoryMockStockGetByIDExpectation { + if mmStockGetByID.mock.funcStockGetByID != nil { + mmStockGetByID.mock.t.Fatalf("StockRepositoryMock.StockGetByID mock is already set by Set") + } + + expectation := &StockRepositoryMockStockGetByIDExpectation{ + mock: mmStockGetByID.mock, + params: &StockRepositoryMockStockGetByIDParams{ctx, sku}, + expectationOrigins: StockRepositoryMockStockGetByIDExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmStockGetByID.expectations = append(mmStockGetByID.expectations, expectation) + return expectation +} + +// Then sets up StockRepository.StockGetByID return parameters for the expectation previously defined by the When method +func (e *StockRepositoryMockStockGetByIDExpectation) Then(sp1 *entity.Stock, err error) *StockRepositoryMock { + e.results = &StockRepositoryMockStockGetByIDResults{sp1, err} + return e.mock +} + +// Times sets number of times StockRepository.StockGetByID should be invoked +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Times(n uint64) *mStockRepositoryMockStockGetByID { + if n == 0 { + mmStockGetByID.mock.t.Fatalf("Times of StockRepositoryMock.StockGetByID mock can not be zero") + } + mm_atomic.StoreUint64(&mmStockGetByID.expectedInvocations, n) + mmStockGetByID.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmStockGetByID +} + +func (mmStockGetByID *mStockRepositoryMockStockGetByID) invocationsDone() bool { + if len(mmStockGetByID.expectations) == 0 && mmStockGetByID.defaultExpectation == nil && mmStockGetByID.mock.funcStockGetByID == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmStockGetByID.mock.afterStockGetByIDCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmStockGetByID.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// StockGetByID implements mm_service.StockRepository +func (mmStockGetByID *StockRepositoryMock) StockGetByID(ctx context.Context, sku entity.Sku) (sp1 *entity.Stock, err error) { + mm_atomic.AddUint64(&mmStockGetByID.beforeStockGetByIDCounter, 1) + defer mm_atomic.AddUint64(&mmStockGetByID.afterStockGetByIDCounter, 1) + + mmStockGetByID.t.Helper() + + if mmStockGetByID.inspectFuncStockGetByID != nil { + mmStockGetByID.inspectFuncStockGetByID(ctx, sku) + } + + mm_params := StockRepositoryMockStockGetByIDParams{ctx, sku} + + // Record call args + mmStockGetByID.StockGetByIDMock.mutex.Lock() + mmStockGetByID.StockGetByIDMock.callArgs = append(mmStockGetByID.StockGetByIDMock.callArgs, &mm_params) + mmStockGetByID.StockGetByIDMock.mutex.Unlock() + + for _, e := range mmStockGetByID.StockGetByIDMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.sp1, e.results.err + } + } + + if mmStockGetByID.StockGetByIDMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmStockGetByID.StockGetByIDMock.defaultExpectation.Counter, 1) + mm_want := mmStockGetByID.StockGetByIDMock.defaultExpectation.params + mm_want_ptrs := mmStockGetByID.StockGetByIDMock.defaultExpectation.paramPtrs + + mm_got := StockRepositoryMockStockGetByIDParams{ctx, sku} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmStockGetByID.t.Errorf("StockRepositoryMock.StockGetByID got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockGetByID.StockGetByIDMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.sku != nil && !minimock.Equal(*mm_want_ptrs.sku, mm_got.sku) { + mmStockGetByID.t.Errorf("StockRepositoryMock.StockGetByID got unexpected parameter sku, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockGetByID.StockGetByIDMock.defaultExpectation.expectationOrigins.originSku, *mm_want_ptrs.sku, mm_got.sku, minimock.Diff(*mm_want_ptrs.sku, mm_got.sku)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmStockGetByID.t.Errorf("StockRepositoryMock.StockGetByID got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockGetByID.StockGetByIDMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmStockGetByID.StockGetByIDMock.defaultExpectation.results + if mm_results == nil { + mmStockGetByID.t.Fatal("No results are set for the StockRepositoryMock.StockGetByID") + } + return (*mm_results).sp1, (*mm_results).err + } + if mmStockGetByID.funcStockGetByID != nil { + return mmStockGetByID.funcStockGetByID(ctx, sku) + } + mmStockGetByID.t.Fatalf("Unexpected call to StockRepositoryMock.StockGetByID. %v %v", ctx, sku) + return +} + +// StockGetByIDAfterCounter returns a count of finished StockRepositoryMock.StockGetByID invocations +func (mmStockGetByID *StockRepositoryMock) StockGetByIDAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockGetByID.afterStockGetByIDCounter) +} + +// StockGetByIDBeforeCounter returns a count of StockRepositoryMock.StockGetByID invocations +func (mmStockGetByID *StockRepositoryMock) StockGetByIDBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockGetByID.beforeStockGetByIDCounter) +} + +// Calls returns a list of arguments used in each call to StockRepositoryMock.StockGetByID. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmStockGetByID *mStockRepositoryMockStockGetByID) Calls() []*StockRepositoryMockStockGetByIDParams { + mmStockGetByID.mutex.RLock() + + argCopy := make([]*StockRepositoryMockStockGetByIDParams, len(mmStockGetByID.callArgs)) + copy(argCopy, mmStockGetByID.callArgs) + + mmStockGetByID.mutex.RUnlock() + + return argCopy +} + +// MinimockStockGetByIDDone returns true if the count of the StockGetByID invocations corresponds +// the number of defined expectations +func (m *StockRepositoryMock) MinimockStockGetByIDDone() bool { + if m.StockGetByIDMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.StockGetByIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.StockGetByIDMock.invocationsDone() +} + +// MinimockStockGetByIDInspect logs each unmet expectation +func (m *StockRepositoryMock) MinimockStockGetByIDInspect() { + for _, e := range m.StockGetByIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockGetByID at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterStockGetByIDCounter := mm_atomic.LoadUint64(&m.afterStockGetByIDCounter) + // if default expectation was set then invocations count should be greater than zero + if m.StockGetByIDMock.defaultExpectation != nil && afterStockGetByIDCounter < 1 { + if m.StockGetByIDMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to StockRepositoryMock.StockGetByID at\n%s", m.StockGetByIDMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to StockRepositoryMock.StockGetByID at\n%s with params: %#v", m.StockGetByIDMock.defaultExpectation.expectationOrigins.origin, *m.StockGetByIDMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcStockGetByID != nil && afterStockGetByIDCounter < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockGetByID at\n%s", m.funcStockGetByIDOrigin) + } + + if !m.StockGetByIDMock.invocationsDone() && afterStockGetByIDCounter > 0 { + m.t.Errorf("Expected %d calls to StockRepositoryMock.StockGetByID at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.StockGetByIDMock.expectedInvocations), m.StockGetByIDMock.expectedInvocationsOrigin, afterStockGetByIDCounter) + } +} + +type mStockRepositoryMockStockReserve struct { + optional bool + mock *StockRepositoryMock + defaultExpectation *StockRepositoryMockStockReserveExpectation + expectations []*StockRepositoryMockStockReserveExpectation + + callArgs []*StockRepositoryMockStockReserveParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// StockRepositoryMockStockReserveExpectation specifies expectation struct of the StockRepository.StockReserve +type StockRepositoryMockStockReserveExpectation struct { + mock *StockRepositoryMock + params *StockRepositoryMockStockReserveParams + paramPtrs *StockRepositoryMockStockReserveParamPtrs + expectationOrigins StockRepositoryMockStockReserveExpectationOrigins + results *StockRepositoryMockStockReserveResults + returnOrigin string + Counter uint64 +} + +// StockRepositoryMockStockReserveParams contains parameters of the StockRepository.StockReserve +type StockRepositoryMockStockReserveParams struct { + ctx context.Context + stock *entity.Stock +} + +// StockRepositoryMockStockReserveParamPtrs contains pointers to parameters of the StockRepository.StockReserve +type StockRepositoryMockStockReserveParamPtrs struct { + ctx *context.Context + stock **entity.Stock +} + +// StockRepositoryMockStockReserveResults contains results of the StockRepository.StockReserve +type StockRepositoryMockStockReserveResults struct { + err error +} + +// StockRepositoryMockStockReserveOrigins contains origins of expectations of the StockRepository.StockReserve +type StockRepositoryMockStockReserveExpectationOrigins struct { + origin string + originCtx string + originStock string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmStockReserve *mStockRepositoryMockStockReserve) Optional() *mStockRepositoryMockStockReserve { + mmStockReserve.optional = true + return mmStockReserve +} + +// Expect sets up expected params for StockRepository.StockReserve +func (mmStockReserve *mStockRepositoryMockStockReserve) Expect(ctx context.Context, stock *entity.Stock) *mStockRepositoryMockStockReserve { + if mmStockReserve.mock.funcStockReserve != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Set") + } + + if mmStockReserve.defaultExpectation == nil { + mmStockReserve.defaultExpectation = &StockRepositoryMockStockReserveExpectation{} + } + + if mmStockReserve.defaultExpectation.paramPtrs != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by ExpectParams functions") + } + + mmStockReserve.defaultExpectation.params = &StockRepositoryMockStockReserveParams{ctx, stock} + mmStockReserve.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmStockReserve.expectations { + if minimock.Equal(e.params, mmStockReserve.defaultExpectation.params) { + mmStockReserve.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmStockReserve.defaultExpectation.params) + } + } + + return mmStockReserve +} + +// ExpectCtxParam1 sets up expected param ctx for StockRepository.StockReserve +func (mmStockReserve *mStockRepositoryMockStockReserve) ExpectCtxParam1(ctx context.Context) *mStockRepositoryMockStockReserve { + if mmStockReserve.mock.funcStockReserve != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Set") + } + + if mmStockReserve.defaultExpectation == nil { + mmStockReserve.defaultExpectation = &StockRepositoryMockStockReserveExpectation{} + } + + if mmStockReserve.defaultExpectation.params != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Expect") + } + + if mmStockReserve.defaultExpectation.paramPtrs == nil { + mmStockReserve.defaultExpectation.paramPtrs = &StockRepositoryMockStockReserveParamPtrs{} + } + mmStockReserve.defaultExpectation.paramPtrs.ctx = &ctx + mmStockReserve.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmStockReserve +} + +// ExpectStockParam2 sets up expected param stock for StockRepository.StockReserve +func (mmStockReserve *mStockRepositoryMockStockReserve) ExpectStockParam2(stock *entity.Stock) *mStockRepositoryMockStockReserve { + if mmStockReserve.mock.funcStockReserve != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Set") + } + + if mmStockReserve.defaultExpectation == nil { + mmStockReserve.defaultExpectation = &StockRepositoryMockStockReserveExpectation{} + } + + if mmStockReserve.defaultExpectation.params != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Expect") + } + + if mmStockReserve.defaultExpectation.paramPtrs == nil { + mmStockReserve.defaultExpectation.paramPtrs = &StockRepositoryMockStockReserveParamPtrs{} + } + mmStockReserve.defaultExpectation.paramPtrs.stock = &stock + mmStockReserve.defaultExpectation.expectationOrigins.originStock = minimock.CallerInfo(1) + + return mmStockReserve +} + +// Inspect accepts an inspector function that has same arguments as the StockRepository.StockReserve +func (mmStockReserve *mStockRepositoryMockStockReserve) Inspect(f func(ctx context.Context, stock *entity.Stock)) *mStockRepositoryMockStockReserve { + if mmStockReserve.mock.inspectFuncStockReserve != nil { + mmStockReserve.mock.t.Fatalf("Inspect function is already set for StockRepositoryMock.StockReserve") + } + + mmStockReserve.mock.inspectFuncStockReserve = f + + return mmStockReserve +} + +// Return sets up results that will be returned by StockRepository.StockReserve +func (mmStockReserve *mStockRepositoryMockStockReserve) Return(err error) *StockRepositoryMock { + if mmStockReserve.mock.funcStockReserve != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Set") + } + + if mmStockReserve.defaultExpectation == nil { + mmStockReserve.defaultExpectation = &StockRepositoryMockStockReserveExpectation{mock: mmStockReserve.mock} + } + mmStockReserve.defaultExpectation.results = &StockRepositoryMockStockReserveResults{err} + mmStockReserve.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmStockReserve.mock +} + +// Set uses given function f to mock the StockRepository.StockReserve method +func (mmStockReserve *mStockRepositoryMockStockReserve) Set(f func(ctx context.Context, stock *entity.Stock) (err error)) *StockRepositoryMock { + if mmStockReserve.defaultExpectation != nil { + mmStockReserve.mock.t.Fatalf("Default expectation is already set for the StockRepository.StockReserve method") + } + + if len(mmStockReserve.expectations) > 0 { + mmStockReserve.mock.t.Fatalf("Some expectations are already set for the StockRepository.StockReserve method") + } + + mmStockReserve.mock.funcStockReserve = f + mmStockReserve.mock.funcStockReserveOrigin = minimock.CallerInfo(1) + return mmStockReserve.mock +} + +// When sets expectation for the StockRepository.StockReserve which will trigger the result defined by the following +// Then helper +func (mmStockReserve *mStockRepositoryMockStockReserve) When(ctx context.Context, stock *entity.Stock) *StockRepositoryMockStockReserveExpectation { + if mmStockReserve.mock.funcStockReserve != nil { + mmStockReserve.mock.t.Fatalf("StockRepositoryMock.StockReserve mock is already set by Set") + } + + expectation := &StockRepositoryMockStockReserveExpectation{ + mock: mmStockReserve.mock, + params: &StockRepositoryMockStockReserveParams{ctx, stock}, + expectationOrigins: StockRepositoryMockStockReserveExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmStockReserve.expectations = append(mmStockReserve.expectations, expectation) + return expectation +} + +// Then sets up StockRepository.StockReserve return parameters for the expectation previously defined by the When method +func (e *StockRepositoryMockStockReserveExpectation) Then(err error) *StockRepositoryMock { + e.results = &StockRepositoryMockStockReserveResults{err} + return e.mock +} + +// Times sets number of times StockRepository.StockReserve should be invoked +func (mmStockReserve *mStockRepositoryMockStockReserve) Times(n uint64) *mStockRepositoryMockStockReserve { + if n == 0 { + mmStockReserve.mock.t.Fatalf("Times of StockRepositoryMock.StockReserve mock can not be zero") + } + mm_atomic.StoreUint64(&mmStockReserve.expectedInvocations, n) + mmStockReserve.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmStockReserve +} + +func (mmStockReserve *mStockRepositoryMockStockReserve) invocationsDone() bool { + if len(mmStockReserve.expectations) == 0 && mmStockReserve.defaultExpectation == nil && mmStockReserve.mock.funcStockReserve == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmStockReserve.mock.afterStockReserveCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmStockReserve.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// StockReserve implements mm_service.StockRepository +func (mmStockReserve *StockRepositoryMock) StockReserve(ctx context.Context, stock *entity.Stock) (err error) { + mm_atomic.AddUint64(&mmStockReserve.beforeStockReserveCounter, 1) + defer mm_atomic.AddUint64(&mmStockReserve.afterStockReserveCounter, 1) + + mmStockReserve.t.Helper() + + if mmStockReserve.inspectFuncStockReserve != nil { + mmStockReserve.inspectFuncStockReserve(ctx, stock) + } + + mm_params := StockRepositoryMockStockReserveParams{ctx, stock} + + // Record call args + mmStockReserve.StockReserveMock.mutex.Lock() + mmStockReserve.StockReserveMock.callArgs = append(mmStockReserve.StockReserveMock.callArgs, &mm_params) + mmStockReserve.StockReserveMock.mutex.Unlock() + + for _, e := range mmStockReserve.StockReserveMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmStockReserve.StockReserveMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmStockReserve.StockReserveMock.defaultExpectation.Counter, 1) + mm_want := mmStockReserve.StockReserveMock.defaultExpectation.params + mm_want_ptrs := mmStockReserve.StockReserveMock.defaultExpectation.paramPtrs + + mm_got := StockRepositoryMockStockReserveParams{ctx, stock} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmStockReserve.t.Errorf("StockRepositoryMock.StockReserve got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserve.StockReserveMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.stock != nil && !minimock.Equal(*mm_want_ptrs.stock, mm_got.stock) { + mmStockReserve.t.Errorf("StockRepositoryMock.StockReserve got unexpected parameter stock, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserve.StockReserveMock.defaultExpectation.expectationOrigins.originStock, *mm_want_ptrs.stock, mm_got.stock, minimock.Diff(*mm_want_ptrs.stock, mm_got.stock)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmStockReserve.t.Errorf("StockRepositoryMock.StockReserve got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserve.StockReserveMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmStockReserve.StockReserveMock.defaultExpectation.results + if mm_results == nil { + mmStockReserve.t.Fatal("No results are set for the StockRepositoryMock.StockReserve") + } + return (*mm_results).err + } + if mmStockReserve.funcStockReserve != nil { + return mmStockReserve.funcStockReserve(ctx, stock) + } + mmStockReserve.t.Fatalf("Unexpected call to StockRepositoryMock.StockReserve. %v %v", ctx, stock) + return +} + +// StockReserveAfterCounter returns a count of finished StockRepositoryMock.StockReserve invocations +func (mmStockReserve *StockRepositoryMock) StockReserveAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockReserve.afterStockReserveCounter) +} + +// StockReserveBeforeCounter returns a count of StockRepositoryMock.StockReserve invocations +func (mmStockReserve *StockRepositoryMock) StockReserveBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockReserve.beforeStockReserveCounter) +} + +// Calls returns a list of arguments used in each call to StockRepositoryMock.StockReserve. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmStockReserve *mStockRepositoryMockStockReserve) Calls() []*StockRepositoryMockStockReserveParams { + mmStockReserve.mutex.RLock() + + argCopy := make([]*StockRepositoryMockStockReserveParams, len(mmStockReserve.callArgs)) + copy(argCopy, mmStockReserve.callArgs) + + mmStockReserve.mutex.RUnlock() + + return argCopy +} + +// MinimockStockReserveDone returns true if the count of the StockReserve invocations corresponds +// the number of defined expectations +func (m *StockRepositoryMock) MinimockStockReserveDone() bool { + if m.StockReserveMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.StockReserveMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.StockReserveMock.invocationsDone() +} + +// MinimockStockReserveInspect logs each unmet expectation +func (m *StockRepositoryMock) MinimockStockReserveInspect() { + for _, e := range m.StockReserveMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserve at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterStockReserveCounter := mm_atomic.LoadUint64(&m.afterStockReserveCounter) + // if default expectation was set then invocations count should be greater than zero + if m.StockReserveMock.defaultExpectation != nil && afterStockReserveCounter < 1 { + if m.StockReserveMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserve at\n%s", m.StockReserveMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserve at\n%s with params: %#v", m.StockReserveMock.defaultExpectation.expectationOrigins.origin, *m.StockReserveMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcStockReserve != nil && afterStockReserveCounter < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserve at\n%s", m.funcStockReserveOrigin) + } + + if !m.StockReserveMock.invocationsDone() && afterStockReserveCounter > 0 { + m.t.Errorf("Expected %d calls to StockRepositoryMock.StockReserve at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.StockReserveMock.expectedInvocations), m.StockReserveMock.expectedInvocationsOrigin, afterStockReserveCounter) + } +} + +type mStockRepositoryMockStockReserveRemove struct { + optional bool + mock *StockRepositoryMock + defaultExpectation *StockRepositoryMockStockReserveRemoveExpectation + expectations []*StockRepositoryMockStockReserveRemoveExpectation + + callArgs []*StockRepositoryMockStockReserveRemoveParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// StockRepositoryMockStockReserveRemoveExpectation specifies expectation struct of the StockRepository.StockReserveRemove +type StockRepositoryMockStockReserveRemoveExpectation struct { + mock *StockRepositoryMock + params *StockRepositoryMockStockReserveRemoveParams + paramPtrs *StockRepositoryMockStockReserveRemoveParamPtrs + expectationOrigins StockRepositoryMockStockReserveRemoveExpectationOrigins + results *StockRepositoryMockStockReserveRemoveResults + returnOrigin string + Counter uint64 +} + +// StockRepositoryMockStockReserveRemoveParams contains parameters of the StockRepository.StockReserveRemove +type StockRepositoryMockStockReserveRemoveParams struct { + ctx context.Context + stock *entity.Stock +} + +// StockRepositoryMockStockReserveRemoveParamPtrs contains pointers to parameters of the StockRepository.StockReserveRemove +type StockRepositoryMockStockReserveRemoveParamPtrs struct { + ctx *context.Context + stock **entity.Stock +} + +// StockRepositoryMockStockReserveRemoveResults contains results of the StockRepository.StockReserveRemove +type StockRepositoryMockStockReserveRemoveResults struct { + err error +} + +// StockRepositoryMockStockReserveRemoveOrigins contains origins of expectations of the StockRepository.StockReserveRemove +type StockRepositoryMockStockReserveRemoveExpectationOrigins struct { + origin string + originCtx string + originStock string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Optional() *mStockRepositoryMockStockReserveRemove { + mmStockReserveRemove.optional = true + return mmStockReserveRemove +} + +// Expect sets up expected params for StockRepository.StockReserveRemove +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Expect(ctx context.Context, stock *entity.Stock) *mStockRepositoryMockStockReserveRemove { + if mmStockReserveRemove.mock.funcStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Set") + } + + if mmStockReserveRemove.defaultExpectation == nil { + mmStockReserveRemove.defaultExpectation = &StockRepositoryMockStockReserveRemoveExpectation{} + } + + if mmStockReserveRemove.defaultExpectation.paramPtrs != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by ExpectParams functions") + } + + mmStockReserveRemove.defaultExpectation.params = &StockRepositoryMockStockReserveRemoveParams{ctx, stock} + mmStockReserveRemove.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmStockReserveRemove.expectations { + if minimock.Equal(e.params, mmStockReserveRemove.defaultExpectation.params) { + mmStockReserveRemove.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmStockReserveRemove.defaultExpectation.params) + } + } + + return mmStockReserveRemove +} + +// ExpectCtxParam1 sets up expected param ctx for StockRepository.StockReserveRemove +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) ExpectCtxParam1(ctx context.Context) *mStockRepositoryMockStockReserveRemove { + if mmStockReserveRemove.mock.funcStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Set") + } + + if mmStockReserveRemove.defaultExpectation == nil { + mmStockReserveRemove.defaultExpectation = &StockRepositoryMockStockReserveRemoveExpectation{} + } + + if mmStockReserveRemove.defaultExpectation.params != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Expect") + } + + if mmStockReserveRemove.defaultExpectation.paramPtrs == nil { + mmStockReserveRemove.defaultExpectation.paramPtrs = &StockRepositoryMockStockReserveRemoveParamPtrs{} + } + mmStockReserveRemove.defaultExpectation.paramPtrs.ctx = &ctx + mmStockReserveRemove.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmStockReserveRemove +} + +// ExpectStockParam2 sets up expected param stock for StockRepository.StockReserveRemove +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) ExpectStockParam2(stock *entity.Stock) *mStockRepositoryMockStockReserveRemove { + if mmStockReserveRemove.mock.funcStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Set") + } + + if mmStockReserveRemove.defaultExpectation == nil { + mmStockReserveRemove.defaultExpectation = &StockRepositoryMockStockReserveRemoveExpectation{} + } + + if mmStockReserveRemove.defaultExpectation.params != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Expect") + } + + if mmStockReserveRemove.defaultExpectation.paramPtrs == nil { + mmStockReserveRemove.defaultExpectation.paramPtrs = &StockRepositoryMockStockReserveRemoveParamPtrs{} + } + mmStockReserveRemove.defaultExpectation.paramPtrs.stock = &stock + mmStockReserveRemove.defaultExpectation.expectationOrigins.originStock = minimock.CallerInfo(1) + + return mmStockReserveRemove +} + +// Inspect accepts an inspector function that has same arguments as the StockRepository.StockReserveRemove +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Inspect(f func(ctx context.Context, stock *entity.Stock)) *mStockRepositoryMockStockReserveRemove { + if mmStockReserveRemove.mock.inspectFuncStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("Inspect function is already set for StockRepositoryMock.StockReserveRemove") + } + + mmStockReserveRemove.mock.inspectFuncStockReserveRemove = f + + return mmStockReserveRemove +} + +// Return sets up results that will be returned by StockRepository.StockReserveRemove +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Return(err error) *StockRepositoryMock { + if mmStockReserveRemove.mock.funcStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Set") + } + + if mmStockReserveRemove.defaultExpectation == nil { + mmStockReserveRemove.defaultExpectation = &StockRepositoryMockStockReserveRemoveExpectation{mock: mmStockReserveRemove.mock} + } + mmStockReserveRemove.defaultExpectation.results = &StockRepositoryMockStockReserveRemoveResults{err} + mmStockReserveRemove.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmStockReserveRemove.mock +} + +// Set uses given function f to mock the StockRepository.StockReserveRemove method +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Set(f func(ctx context.Context, stock *entity.Stock) (err error)) *StockRepositoryMock { + if mmStockReserveRemove.defaultExpectation != nil { + mmStockReserveRemove.mock.t.Fatalf("Default expectation is already set for the StockRepository.StockReserveRemove method") + } + + if len(mmStockReserveRemove.expectations) > 0 { + mmStockReserveRemove.mock.t.Fatalf("Some expectations are already set for the StockRepository.StockReserveRemove method") + } + + mmStockReserveRemove.mock.funcStockReserveRemove = f + mmStockReserveRemove.mock.funcStockReserveRemoveOrigin = minimock.CallerInfo(1) + return mmStockReserveRemove.mock +} + +// When sets expectation for the StockRepository.StockReserveRemove which will trigger the result defined by the following +// Then helper +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) When(ctx context.Context, stock *entity.Stock) *StockRepositoryMockStockReserveRemoveExpectation { + if mmStockReserveRemove.mock.funcStockReserveRemove != nil { + mmStockReserveRemove.mock.t.Fatalf("StockRepositoryMock.StockReserveRemove mock is already set by Set") + } + + expectation := &StockRepositoryMockStockReserveRemoveExpectation{ + mock: mmStockReserveRemove.mock, + params: &StockRepositoryMockStockReserveRemoveParams{ctx, stock}, + expectationOrigins: StockRepositoryMockStockReserveRemoveExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmStockReserveRemove.expectations = append(mmStockReserveRemove.expectations, expectation) + return expectation +} + +// Then sets up StockRepository.StockReserveRemove return parameters for the expectation previously defined by the When method +func (e *StockRepositoryMockStockReserveRemoveExpectation) Then(err error) *StockRepositoryMock { + e.results = &StockRepositoryMockStockReserveRemoveResults{err} + return e.mock +} + +// Times sets number of times StockRepository.StockReserveRemove should be invoked +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Times(n uint64) *mStockRepositoryMockStockReserveRemove { + if n == 0 { + mmStockReserveRemove.mock.t.Fatalf("Times of StockRepositoryMock.StockReserveRemove mock can not be zero") + } + mm_atomic.StoreUint64(&mmStockReserveRemove.expectedInvocations, n) + mmStockReserveRemove.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmStockReserveRemove +} + +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) invocationsDone() bool { + if len(mmStockReserveRemove.expectations) == 0 && mmStockReserveRemove.defaultExpectation == nil && mmStockReserveRemove.mock.funcStockReserveRemove == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmStockReserveRemove.mock.afterStockReserveRemoveCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmStockReserveRemove.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// StockReserveRemove implements mm_service.StockRepository +func (mmStockReserveRemove *StockRepositoryMock) StockReserveRemove(ctx context.Context, stock *entity.Stock) (err error) { + mm_atomic.AddUint64(&mmStockReserveRemove.beforeStockReserveRemoveCounter, 1) + defer mm_atomic.AddUint64(&mmStockReserveRemove.afterStockReserveRemoveCounter, 1) + + mmStockReserveRemove.t.Helper() + + if mmStockReserveRemove.inspectFuncStockReserveRemove != nil { + mmStockReserveRemove.inspectFuncStockReserveRemove(ctx, stock) + } + + mm_params := StockRepositoryMockStockReserveRemoveParams{ctx, stock} + + // Record call args + mmStockReserveRemove.StockReserveRemoveMock.mutex.Lock() + mmStockReserveRemove.StockReserveRemoveMock.callArgs = append(mmStockReserveRemove.StockReserveRemoveMock.callArgs, &mm_params) + mmStockReserveRemove.StockReserveRemoveMock.mutex.Unlock() + + for _, e := range mmStockReserveRemove.StockReserveRemoveMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.Counter, 1) + mm_want := mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.params + mm_want_ptrs := mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.paramPtrs + + mm_got := StockRepositoryMockStockReserveRemoveParams{ctx, stock} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmStockReserveRemove.t.Errorf("StockRepositoryMock.StockReserveRemove got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.stock != nil && !minimock.Equal(*mm_want_ptrs.stock, mm_got.stock) { + mmStockReserveRemove.t.Errorf("StockRepositoryMock.StockReserveRemove got unexpected parameter stock, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.expectationOrigins.originStock, *mm_want_ptrs.stock, mm_got.stock, minimock.Diff(*mm_want_ptrs.stock, mm_got.stock)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmStockReserveRemove.t.Errorf("StockRepositoryMock.StockReserveRemove got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmStockReserveRemove.StockReserveRemoveMock.defaultExpectation.results + if mm_results == nil { + mmStockReserveRemove.t.Fatal("No results are set for the StockRepositoryMock.StockReserveRemove") + } + return (*mm_results).err + } + if mmStockReserveRemove.funcStockReserveRemove != nil { + return mmStockReserveRemove.funcStockReserveRemove(ctx, stock) + } + mmStockReserveRemove.t.Fatalf("Unexpected call to StockRepositoryMock.StockReserveRemove. %v %v", ctx, stock) + return +} + +// StockReserveRemoveAfterCounter returns a count of finished StockRepositoryMock.StockReserveRemove invocations +func (mmStockReserveRemove *StockRepositoryMock) StockReserveRemoveAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockReserveRemove.afterStockReserveRemoveCounter) +} + +// StockReserveRemoveBeforeCounter returns a count of StockRepositoryMock.StockReserveRemove invocations +func (mmStockReserveRemove *StockRepositoryMock) StockReserveRemoveBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmStockReserveRemove.beforeStockReserveRemoveCounter) +} + +// Calls returns a list of arguments used in each call to StockRepositoryMock.StockReserveRemove. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmStockReserveRemove *mStockRepositoryMockStockReserveRemove) Calls() []*StockRepositoryMockStockReserveRemoveParams { + mmStockReserveRemove.mutex.RLock() + + argCopy := make([]*StockRepositoryMockStockReserveRemoveParams, len(mmStockReserveRemove.callArgs)) + copy(argCopy, mmStockReserveRemove.callArgs) + + mmStockReserveRemove.mutex.RUnlock() + + return argCopy +} + +// MinimockStockReserveRemoveDone returns true if the count of the StockReserveRemove invocations corresponds +// the number of defined expectations +func (m *StockRepositoryMock) MinimockStockReserveRemoveDone() bool { + if m.StockReserveRemoveMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.StockReserveRemoveMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.StockReserveRemoveMock.invocationsDone() +} + +// MinimockStockReserveRemoveInspect logs each unmet expectation +func (m *StockRepositoryMock) MinimockStockReserveRemoveInspect() { + for _, e := range m.StockReserveRemoveMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserveRemove at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterStockReserveRemoveCounter := mm_atomic.LoadUint64(&m.afterStockReserveRemoveCounter) + // if default expectation was set then invocations count should be greater than zero + if m.StockReserveRemoveMock.defaultExpectation != nil && afterStockReserveRemoveCounter < 1 { + if m.StockReserveRemoveMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserveRemove at\n%s", m.StockReserveRemoveMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserveRemove at\n%s with params: %#v", m.StockReserveRemoveMock.defaultExpectation.expectationOrigins.origin, *m.StockReserveRemoveMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcStockReserveRemove != nil && afterStockReserveRemoveCounter < 1 { + m.t.Errorf("Expected call to StockRepositoryMock.StockReserveRemove at\n%s", m.funcStockReserveRemoveOrigin) + } + + if !m.StockReserveRemoveMock.invocationsDone() && afterStockReserveRemoveCounter > 0 { + m.t.Errorf("Expected %d calls to StockRepositoryMock.StockReserveRemove at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.StockReserveRemoveMock.expectedInvocations), m.StockReserveRemoveMock.expectedInvocationsOrigin, afterStockReserveRemoveCounter) + } +} + +// MinimockFinish checks that all mocked methods have been called the expected number of times +func (m *StockRepositoryMock) MinimockFinish() { + m.finishOnce.Do(func() { + if !m.minimockDone() { + m.MinimockStockCancelInspect() + + m.MinimockStockGetByIDInspect() + + m.MinimockStockReserveInspect() + + m.MinimockStockReserveRemoveInspect() + } + }) +} + +// MinimockWait waits for all mocked methods to be called the expected number of times +func (m *StockRepositoryMock) MinimockWait(timeout mm_time.Duration) { + timeoutCh := mm_time.After(timeout) + for { + if m.minimockDone() { + return + } + select { + case <-timeoutCh: + m.MinimockFinish() + return + case <-mm_time.After(10 * mm_time.Millisecond): + } + } +} + +func (m *StockRepositoryMock) minimockDone() bool { + done := true + return done && + m.MinimockStockCancelDone() && + m.MinimockStockGetByIDDone() && + m.MinimockStockReserveDone() && + m.MinimockStockReserveRemoveDone() +} diff --git a/loms/internal/domain/service/service.go b/loms/internal/domain/service/service.go new file mode 100644 index 0000000..b579e69 --- /dev/null +++ b/loms/internal/domain/service/service.go @@ -0,0 +1,186 @@ +package service + +import ( + "context" + "fmt" + "slices" + + "route256/loms/internal/domain/entity" + "route256/loms/internal/domain/model" + + pb "route256/pkg/api/loms/v1" + + "github.com/rs/zerolog/log" +) + +//go:generate minimock -i OrderRepository -o ./mock -s _mock.go +type OrderRepository interface { + OrderCreate(ctx context.Context, order *entity.Order) (entity.ID, error) + OrderSetStatus(ctx context.Context, orderID entity.ID, newStatus string) error + OrderGetByID(ctx context.Context, orderID entity.ID) (*entity.Order, error) +} + +//go:generate minimock -i StockRepository -o ./mock -s _mock.go +type StockRepository interface { + StockReserve(ctx context.Context, stock *entity.Stock) error + StockReserveRemove(ctx context.Context, stock *entity.Stock) error + StockCancel(ctx context.Context, stock *entity.Stock) error + StockGetByID(ctx context.Context, sku entity.Sku) (*entity.Stock, error) +} + +type LomsService struct { + orders OrderRepository + stocks StockRepository +} + +func NewLomsService(orderRepo OrderRepository, stockRepo StockRepository) *LomsService { + return &LomsService{ + orders: orderRepo, + stocks: stockRepo, + } +} + +func (s *LomsService) rollbackStocks(ctx context.Context, stocks []*entity.Stock) { + for _, stock := range stocks { + if err := s.stocks.StockCancel(ctx, stock); err != nil { + log.Error().Err(err).Msg("failed to rollback stock") + } + } +} + +func (s *LomsService) OrderCreate(ctx context.Context, orderReq *pb.OrderCreateRequest) (entity.ID, error) { + if orderReq == nil || orderReq.UserId <= 0 || len(orderReq.Items) == 0 { + return 0, model.ErrInvalidInput + } + + for _, item := range orderReq.Items { + if item.Sku <= 0 || item.Count == 0 { + return 0, model.ErrInvalidInput + } + } + + order := &entity.Order{ + OrderID: 0, + Status: pb.OrderStatus_ORDER_STATUS_NEW.String(), + UserID: entity.ID(orderReq.UserId), + Items: make([]entity.OrderItem, len(orderReq.Items)), + } + + for i, item := range orderReq.Items { + order.Items[i] = entity.OrderItem{ + ID: entity.Sku(item.Sku), + Count: item.Count, + } + } + + slices.SortStableFunc(order.Items, func(a, b entity.OrderItem) int { + return int(a.ID - b.ID) + }) + + id, err := s.orders.OrderCreate(ctx, order) + if err != nil { + return 0, fmt.Errorf("orders.OrderCreate: %w", err) + } + + order.OrderID = id + + commitedStocks := make([]*entity.Stock, 0, len(order.Items)) + for _, item := range order.Items { + stock := &entity.Stock{ + Item: item, + Reserved: item.Count, + } + + if err := s.stocks.StockReserve(ctx, stock); err != nil { + s.rollbackStocks(ctx, commitedStocks) + + if statusErr := s.orders.OrderSetStatus(ctx, order.OrderID, pb.OrderStatus_ORDER_STATUS_FAILED.String()); statusErr != nil { + log.Error().Err(statusErr).Msg("failed to update status on stock reserve fail") + } + + return 0, fmt.Errorf("stocks.StockReserve: %w", err) + } + + commitedStocks = append(commitedStocks, stock) + } + + if err := s.orders.OrderSetStatus(ctx, order.OrderID, pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String()); err != nil { + s.rollbackStocks(ctx, commitedStocks) + + return 0, err + } + + return order.OrderID, nil +} + +func (s *LomsService) OrderInfo(ctx context.Context, orderID entity.ID) (*entity.Order, error) { + if orderID <= 0 { + return nil, model.ErrInvalidInput + } + + return s.orders.OrderGetByID(ctx, orderID) +} + +func (s *LomsService) OrderPay(ctx context.Context, orderID entity.ID) error { + order, err := s.OrderInfo(ctx, orderID) + if err != nil { + return err + } + + switch order.Status { + case pb.OrderStatus_ORDER_STATUS_PAYED.String(): + return nil + case pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String(): + for _, item := range order.Items { + if err := s.stocks.StockReserveRemove(ctx, &entity.Stock{ + Item: item, + Reserved: item.Count, + }); err != nil { + log.Error().Err(err).Msg("failed to free stock reservation") + } + } + + return s.orders.OrderSetStatus(ctx, orderID, pb.OrderStatus_ORDER_STATUS_PAYED.String()) + default: + return model.ErrOrderInvalidStatus + } +} + +func (s *LomsService) OrderCancel(ctx context.Context, orderID entity.ID) error { + order, err := s.OrderInfo(ctx, orderID) + if err != nil { + return err + } + + switch order.Status { + case pb.OrderStatus_ORDER_STATUS_CANCELLED.String(): + return nil + case pb.OrderStatus_ORDER_STATUS_FAILED.String(), pb.OrderStatus_ORDER_STATUS_PAYED.String(): + return model.ErrOrderInvalidStatus + } + + stocks := make([]*entity.Stock, len(order.Items)) + for i, item := range order.Items { + stocks[i] = &entity.Stock{ + Item: item, + Reserved: item.Count, + } + } + + s.rollbackStocks(ctx, stocks) + + return s.orders.OrderSetStatus(ctx, orderID, pb.OrderStatus_ORDER_STATUS_CANCELLED.String()) +} + +func (s *LomsService) StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error) { + if sku <= 0 { + return 0, model.ErrInvalidInput + } + + stock, err := s.stocks.StockGetByID(ctx, sku) + if err != nil { + return 0, err + } + + return stock.Item.Count, nil +} diff --git a/loms/internal/domain/service/service_test.go b/loms/internal/domain/service/service_test.go new file mode 100644 index 0000000..c5af63f --- /dev/null +++ b/loms/internal/domain/service/service_test.go @@ -0,0 +1,448 @@ +package service + +import ( + "context" + "errors" + "testing" + + "github.com/gojuno/minimock/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "route256/loms/internal/domain/entity" + "route256/loms/internal/domain/model" + mock "route256/loms/internal/domain/service/mock" + + pb "route256/pkg/api/loms/v1" +) + +const ( + testUser = entity.ID(1337) + testSku = entity.Sku(199) +) + +func TestLomsService_OrderCreate(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + goodReq := &pb.OrderCreateRequest{ + UserId: int64(testUser), + Items: []*pb.OrderItem{ + { + Sku: int64(testSku), + Count: 2, + }, + }, + } + + badItemReq := &pb.OrderCreateRequest{ + UserId: int64(testUser), + Items: []*pb.OrderItem{ + { + Sku: 0, + Count: 0, + }, + }, + } + + type fields struct { + orders OrderRepository + stocks StockRepository + } + type args struct { + req *pb.OrderCreateRequest + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderCreateMock.Return(1, nil). + OrderSetStatusMock.Return(nil), + stocks: mock.NewStockRepositoryMock(mc). + StockReserveMock.Return(nil), + }, + args: args{ + req: goodReq, + }, + wantErr: require.NoError, + }, + { + name: "invalid input", + fields: fields{}, + args: args{ + req: &pb.OrderCreateRequest{ + UserId: 0, + }, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrInvalidInput) + }, + }, + { + name: "invalid input with bad items", + fields: fields{}, + args: args{ + req: badItemReq, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrInvalidInput) + }, + }, + { + name: "order create error", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderCreateMock.Return(0, errors.New("order create error")), + stocks: nil, + }, + args: args{ + req: goodReq, + }, + wantErr: require.Error, + }, + { + name: "stock reserve error", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderCreateMock.Return(1, nil). + OrderSetStatusMock.Return(errors.New("status update error")), + stocks: mock.NewStockRepositoryMock(mc). + StockReserveMock.Return(errors.New("reservation error")), + }, + args: args{ + req: goodReq, + }, + wantErr: require.Error, + }, + { + name: "final status update error", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderCreateMock.Return(1, nil). + OrderSetStatusMock.Return(errors.New("unexpected error")), + stocks: mock.NewStockRepositoryMock(mc). + StockReserveMock.Return(nil). + StockCancelMock.Return(nil), + }, + args: args{ + req: goodReq, + }, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := NewLomsService(tt.fields.orders, tt.fields.stocks) + _, err := svc.OrderCreate(ctx, tt.args.req) + tt.wantErr(t, err) + }) + } +} + +func TestLomsService_OrderPay(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + awaitingOrder := &entity.Order{ + OrderID: 1, + Status: pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String(), + Items: []entity.OrderItem{{ + ID: testSku, + Count: 2, + }}, + } + + payedOrder := &entity.Order{OrderID: 2, Status: pb.OrderStatus_ORDER_STATUS_PAYED.String()} + badStatusOrder := &entity.Order{OrderID: 3, Status: pb.OrderStatus_ORDER_STATUS_FAILED.String()} + + type fields struct { + orders OrderRepository + stocks StockRepository + } + type args struct { + id entity.ID + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(awaitingOrder, nil). + OrderSetStatusMock.Return(nil), + stocks: mock.NewStockRepositoryMock(mc). + StockReserveRemoveMock.Return(nil), + }, + args: args{ + id: awaitingOrder.OrderID, + }, + wantErr: require.NoError, + }, + { + name: "invalid input", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc), + stocks: mock.NewStockRepositoryMock(mc), + }, + args: args{ + id: 0, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrInvalidInput) + }, + }, + { + name: "already payed", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(payedOrder, nil), + }, + args: args{ + id: payedOrder.OrderID, + }, + wantErr: require.NoError, + }, + { + name: "invalid status", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(badStatusOrder, nil), + }, + args: args{ + id: badStatusOrder.OrderID, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrOrderInvalidStatus) + }, + }, + { + name: "unexpected logged error on updating stocks reserves", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(awaitingOrder, nil). + OrderSetStatusMock.Return(nil), + stocks: mock.NewStockRepositoryMock(mc). + StockReserveRemoveMock.Return(errors.New("unexpected error")), + }, + args: args{ + id: badStatusOrder.OrderID, + }, + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := NewLomsService(tt.fields.orders, tt.fields.stocks) + err := svc.OrderPay(ctx, tt.args.id) + tt.wantErr(t, err) + }) + } +} + +func TestLomsService_OrderInfo(t *testing.T) { + t.Parallel() + + mc := minimock.NewController(t) + svc := NewLomsService( + mock.NewOrderRepositoryMock(mc), + mock.NewStockRepositoryMock(mc), + ) + + err := svc.OrderPay(context.Background(), 0) + require.ErrorIs(t, err, model.ErrInvalidInput) +} + +func TestLomsService_OrderCancel(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + awaiting := &entity.Order{ + OrderID: 1, + Status: pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String(), + Items: []entity.OrderItem{{ + ID: testSku, Count: 1, + }}, + } + cancelled := &entity.Order{ + OrderID: 2, + Status: pb.OrderStatus_ORDER_STATUS_CANCELLED.String(), + } + payed := &entity.Order{ + OrderID: 3, + Status: pb.OrderStatus_ORDER_STATUS_PAYED.String(), + } + + type fields struct { + orders OrderRepository + stocks StockRepository + } + type args struct { + id entity.ID + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(awaiting, nil). + OrderSetStatusMock.Return(nil), + stocks: mock.NewStockRepositoryMock(mc). + StockCancelMock.Return(nil), + }, + args: args{ + id: awaiting.OrderID, + }, + wantErr: require.NoError, + }, + { + name: "invalid input", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc), + stocks: mock.NewStockRepositoryMock(mc), + }, + args: args{ + id: 0, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrInvalidInput) + }, + }, + { + name: "already cancelled", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(cancelled, nil), + }, + args: args{ + id: cancelled.OrderID, + }, + wantErr: require.NoError, + }, + { + name: "invalid status", + fields: fields{ + orders: mock.NewOrderRepositoryMock(mc). + OrderGetByIDMock.Return(payed, nil), + }, + args: args{ + id: payed.OrderID, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrOrderInvalidStatus) + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := NewLomsService(tt.fields.orders, tt.fields.stocks) + err := svc.OrderCancel(ctx, tt.args.id) + tt.wantErr(t, err) + }) + } +} + +func TestLomsService_StocksInfo(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + type fields struct{ stocks StockRepository } + type args struct{ sku entity.Sku } + + tests := []struct { + name string + fields fields + args args + want uint32 + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + stocks: mock.NewStockRepositoryMock(mc). + StockGetByIDMock.Return(&entity.Stock{ + Item: entity.OrderItem{ + Count: 7, + }, + }, nil), + }, + args: args{ + sku: testSku, + }, + want: 7, + wantErr: require.NoError, + }, + { + name: "invalid sku", + fields: fields{}, + args: args{ + sku: 0, + }, + wantErr: func(t require.TestingT, err error, _ ...interface{}) { + require.ErrorIs(t, err, model.ErrInvalidInput) + }, + }, + { + name: "get by id error", + fields: fields{ + stocks: mock.NewStockRepositoryMock(mc). + StockGetByIDMock.Return(nil, errors.New("unexpected error")), + }, + args: args{ + sku: testSku, + }, + wantErr: require.Error, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := NewLomsService(nil, tt.fields.stocks) + got, err := svc.StocksInfo(ctx, tt.args.sku) + tt.wantErr(t, err) + if err == nil { + assert.Equal(t, tt.want, got) + } + }) + } +} diff --git a/loms/internal/infra/config/config.go b/loms/internal/infra/config/config.go new file mode 100644 index 0000000..dfb32e3 --- /dev/null +++ b/loms/internal/infra/config/config.go @@ -0,0 +1,74 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Service struct { + Host string `yaml:"host"` + GRPCPort string `yaml:"grpc_port"` + HTTPPort string `yaml:"http_port"` + LogLevel string `yaml:"log_level"` + } `yaml:"service"` + + Jaeger struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + } `yaml:"jaeger"` + + DatabaseMaster struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + DBName string `yaml:"db_name"` + } `yaml:"db_master"` + + DatabaseReplica struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + DBName string `yaml:"db_name"` + } `yaml:"db_replica"` + + Kafka struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + OrderTopic string `yaml:"order_topic"` + Brokers string `yaml:"brokers"` + } `yaml:"kafka"` +} + +func LoadConfig(filename string) (*Config, error) { + workDir, err := os.Getwd() + if err != nil { + return nil, err + } + cfgRoot := filepath.Join(workDir, "configs") + absCfgRoot, _ := filepath.Abs(cfgRoot) + + filePath := filepath.Join(absCfgRoot, filepath.Clean(filename)) + if !strings.HasPrefix(filePath, absCfgRoot) { + return nil, fmt.Errorf("invalid path") + } + + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + config := &Config{} + if err := yaml.NewDecoder(f).Decode(config); err != nil { + return nil, err + } + + return config, nil +} diff --git a/loms/internal/infra/grpc/middleware/logging.go b/loms/internal/infra/grpc/middleware/logging.go new file mode 100644 index 0000000..23da530 --- /dev/null +++ b/loms/internal/infra/grpc/middleware/logging.go @@ -0,0 +1,24 @@ +package mw + +import ( + "context" + + "github.com/rs/zerolog/log" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +func Logging(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + raw, _ := protojson.Marshal((req).(proto.Message)) + log.Debug().Msgf("request: method: %v, req: %s", info.FullMethod, string(raw)) + + if resp, err = handler(ctx, req); err != nil { + log.Debug().Msgf("response: method: %v, err: %s", info.FullMethod, err.Error()) + return + } + rawResp, _ := protojson.Marshal((resp).(proto.Message)) + log.Debug().Msgf("response: method: %v, resp: %s", info.FullMethod, string(rawResp)) + + return +} diff --git a/loms/internal/infra/grpc/middleware/validate.go b/loms/internal/infra/grpc/middleware/validate.go new file mode 100644 index 0000000..9aad15a --- /dev/null +++ b/loms/internal/infra/grpc/middleware/validate.go @@ -0,0 +1,18 @@ +package mw + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func Validate(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if v, ok := req.(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + } + return handler(ctx, req) +} diff --git a/loms/tests/integration/loms_integration_test.go b/loms/tests/integration/loms_integration_test.go new file mode 100644 index 0000000..8109044 --- /dev/null +++ b/loms/tests/integration/loms_integration_test.go @@ -0,0 +1,130 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "net" + "testing" + "time" + + "github.com/ozontech/allure-go/pkg/framework/provider" + "github.com/ozontech/allure-go/pkg/framework/suite" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "route256/loms/internal/app/server" + "route256/loms/internal/domain/entity" + ordersRepository "route256/loms/internal/domain/repository/orders" + stocksRepository "route256/loms/internal/domain/repository/stocks" + lomsService "route256/loms/internal/domain/service" + + pb "route256/pkg/api/loms/v1" +) + +const ( + // "total_count": 100, + // "reserved": 35 + testSKU = entity.Sku(1076963) + + testUID = entity.ID(1337) + testCount = uint32(2) +) + +type LomsIntegrationSuite struct { + suite.Suite + + grpcSrv *grpc.Server + grpcConn *grpc.ClientConn + lomsClient pb.LOMSClient +} + +func TestLomsIntegrationSuite(t *testing.T) { + suite.RunSuite(t, new(LomsIntegrationSuite)) +} + +func (s *LomsIntegrationSuite) BeforeAll(t provider.T) { + t.WithNewStep("init cart-service", func(sCtx provider.StepCtx) { + orderRepo := ordersRepository.NewInMemoryRepository(100) + stockRepo, err := stocksRepository.NewInMemoryRepository(100) + sCtx.Require().NoError(err) + + svc := lomsService.NewLomsService(orderRepo, stockRepo) + lomsServer := server.NewServer(svc) + + lis, err := net.Listen("tcp", "127.0.0.1:0") + sCtx.Require().NoError(err) + + s.grpcSrv = grpc.NewServer() + pb.RegisterLOMSServer(s.grpcSrv, lomsServer) + + go func() { _ = s.grpcSrv.Serve(lis) }() + + time.Sleep(50 * time.Millisecond) + + conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) + sCtx.Require().NoError(err) + + s.grpcConn = conn + s.lomsClient = pb.NewLOMSClient(conn) + }) +} + +func (s *LomsIntegrationSuite) AfterAll(t provider.T) { + s.grpcSrv.Stop() + _ = s.grpcConn.Close() +} + +func (s *LomsIntegrationSuite) TestOrderProcessPositive(t provider.T) { + ctx := context.Background() + + var orderID int64 + + t.WithNewStep("create order", func(sCtx provider.StepCtx) { + req := &pb.OrderCreateRequest{ + UserId: int64(testUID), + Items: []*pb.OrderItem{{ + Sku: int64(testSKU), + Count: testCount, + }}, + } + + resp, err := s.lomsClient.OrderCreate(ctx, req) + sCtx.Require().NoError(err) + sCtx.Require().Greater(resp.OrderId, int64(0)) + orderID = resp.OrderId + }) + + t.WithNewStep("verify order info (NEW)", func(sCtx provider.StepCtx) { + resp, err := s.lomsClient.OrderInfo(ctx, &pb.OrderInfoRequest{OrderId: orderID}) + sCtx.Require().NoError(err) + + sCtx.Require().Equal("awaiting payment", resp.Status) + sCtx.Require().Equal(int64(testUID), resp.UserId) + sCtx.Require().Len(resp.Items, 1) + sCtx.Require().Equal(int64(testSKU), resp.Items[0].Sku) + sCtx.Require().Equal(testCount, resp.Items[0].Count) + }) + + t.WithNewStep("pay order", func(sCtx provider.StepCtx) { + _, err := s.lomsClient.OrderPay(ctx, &pb.OrderPayRequest{OrderId: orderID}) + sCtx.Require().NoError(err) + }) + + t.WithNewStep("verify order info (PAYED)", func(sCtx provider.StepCtx) { + resp, err := s.lomsClient.OrderInfo(ctx, &pb.OrderInfoRequest{OrderId: orderID}) + sCtx.Require().NoError(err) + sCtx.Require().Equal("payed", resp.Status) + }) +} + +func (s *LomsIntegrationSuite) TestStocksInfoPositive(t provider.T) { + ctx := context.Background() + + t.WithNewStep("call StocksInfo", func(sCtx provider.StepCtx) { + resp, err := s.lomsClient.StocksInfo(ctx, &pb.StocksInfoRequest{Sku: int64(testSKU)}) + sCtx.Require().NoError(err) + sCtx.Require().Greater(resp.Count, uint32(0)) + }) +} diff --git a/make/generate.mk b/make/generate.mk index aae174a..8961c77 100644 --- a/make/generate.mk +++ b/make/generate.mk @@ -1,4 +1,66 @@ +CURDIR=$(shell pwd) +BINDIR=${CURDIR}/bin +.PHONY: .bin-deps +.bin-deps: + $(info Installing binary dependencies...) + + GOBIN=$(BINDIR) go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 && \ + GOBIN=$(BINDIR) go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 && \ + GOBIN=$(BINDIR) go install github.com/envoyproxy/protoc-gen-validate@v1.0.4 && \ + GOBIN=$(BINDIR) go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.19.1 && \ + GOBIN=$(BINDIR) go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.19.1 && \ + GOBIN=$(BINDIR) go install github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5 + +# Устанавливаем proto описания google/protobuf +vendor-proto/google/protobuf: + git clone -b main --single-branch -n --depth=1 \ + https://github.com/protocolbuffers/protobuf vendor-proto/protobuf &&\ + cd vendor-proto/protobuf &&\ + git sparse-checkout set --no-cone src/google/protobuf &&\ + git checkout + mkdir -p vendor-proto/google + mv vendor-proto/protobuf/src/google/protobuf vendor-proto/google + rm -rf vendor-proto/protobuf + +# Устанавливаем proto описания validate +vendor-proto/validate: + git clone -b main --single-branch --depth=2 \ + https://github.com/bufbuild/protoc-gen-validate vendor-proto/tmp && \ + cd vendor-proto/tmp && \ + git sparse-checkout set --no-cone validate &&\ + git checkout + mkdir -p vendor-proto/validate + mv vendor-proto/tmp/validate vendor-proto/ + rm -rf vendor-proto/tmp + +# Устанавливаем proto описания google/googleapis +vendor-proto/google/api: + git clone -b master --single-branch -n --depth=1 \ + https://github.com/googleapis/googleapis vendor-proto/googleapis && \ + cd vendor-proto/googleapis && \ + git sparse-checkout set --no-cone google/api && \ + git checkout + mkdir -p vendor-proto/google + mv vendor-proto/googleapis/google/api vendor-proto/google + rm -rf vendor-proto/googleapis + +# Устанавливаем proto описания protoc-gen-openapiv2/options +vendor-proto/protoc-gen-openapiv2/options: + git clone -b main --single-branch -n --depth=1 \ + https://github.com/grpc-ecosystem/grpc-gateway vendor-proto/grpc-ecosystem && \ + cd vendor-proto/grpc-ecosystem && \ + git sparse-checkout set --no-cone protoc-gen-openapiv2/options && \ + git checkout + mkdir -p vendor-proto/protoc-gen-openapiv2 + mv vendor-proto/grpc-ecosystem/protoc-gen-openapiv2/options vendor-proto/protoc-gen-openapiv2 + rm -rf vendor-proto/grpc-ecosystem + +.PHONY: .vendor-rm +.vendor-rm: + rm -rf vendor-proto + +.vendor-proto: .vendor-rm vendor-proto/google/protobuf vendor-proto/validate vendor-proto/google/api vendor-proto/protoc-gen-openapiv2/options define generate @if [ -f "$(1)/go.mod" ]; then \ @@ -11,12 +73,35 @@ define generate fi endef +define proto_gen + @echo "== generating $(1) ==" + @protoc \ + -I api \ + -I vendor-proto \ + --plugin=protoc-gen-go=$(BINDIR)/protoc-gen-go \ + --go_out=pkg/api/ --go_opt paths=source_relative \ + --plugin=protoc-gen-go-grpc=$(BINDIR)/protoc-gen-go-grpc \ + --go-grpc_out=pkg/api/ --go-grpc_opt paths=source_relative \ + --plugin=protoc-gen-validate=$(BINDIR)/protoc-gen-validate \ + --validate_out="lang=go,paths=source_relative:pkg/api/" \ + --plugin=protoc-gen-grpc-gateway=$(BINDIR)/protoc-gen-grpc-gateway \ + --grpc-gateway_out=pkg/api/ \ + --grpc-gateway_opt=logtostderr=true,paths=source_relative,generate_unbound_methods=true \ + --plugin=protoc-gen-openapiv2=$(BINDIR)/protoc-gen-openapiv2 \ + --openapiv2_out=api/openapiv2 \ + --openapiv2_opt=logtostderr=true \ + $(1) +endef cart-generate: $(call generate,cart) + cd cart && go mod tidy loms-generate: $(call generate,loms) + mkdir -p "api/openapiv2" + $(foreach f,$(shell find api/loms/v1 -type f -name '*.proto'),$(call proto_gen,$(f),loms)) + cd loms && go mod tidy notifier-generate: $(call generate,notifier) diff --git a/pkg/api/loms/v1/loms.pb.go b/pkg/api/loms/v1/loms.pb.go new file mode 100644 index 0000000..ba164c2 --- /dev/null +++ b/pkg/api/loms/v1/loms.pb.go @@ -0,0 +1,827 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v6.30.0 +// source: loms/v1/loms.proto + +package loms + +import ( + _ "github.com/envoyproxy/protoc-gen-validate/validate" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type OrderStatus int32 + +const ( + OrderStatus_ORDER_STATUS_UNSPECIFIED OrderStatus = 0 + OrderStatus_ORDER_STATUS_NEW OrderStatus = 1 + OrderStatus_ORDER_STATUS_AWAITING_PAYMENT OrderStatus = 2 + OrderStatus_ORDER_STATUS_FAILED OrderStatus = 3 + OrderStatus_ORDER_STATUS_PAYED OrderStatus = 4 + OrderStatus_ORDER_STATUS_CANCELLED OrderStatus = 5 +) + +// Enum value maps for OrderStatus. +var ( + OrderStatus_name = map[int32]string{ + 0: "ORDER_STATUS_UNSPECIFIED", + 1: "ORDER_STATUS_NEW", + 2: "ORDER_STATUS_AWAITING_PAYMENT", + 3: "ORDER_STATUS_FAILED", + 4: "ORDER_STATUS_PAYED", + 5: "ORDER_STATUS_CANCELLED", + } + OrderStatus_value = map[string]int32{ + "ORDER_STATUS_UNSPECIFIED": 0, + "ORDER_STATUS_NEW": 1, + "ORDER_STATUS_AWAITING_PAYMENT": 2, + "ORDER_STATUS_FAILED": 3, + "ORDER_STATUS_PAYED": 4, + "ORDER_STATUS_CANCELLED": 5, + } +) + +func (x OrderStatus) Enum() *OrderStatus { + p := new(OrderStatus) + *p = x + return p +} + +func (x OrderStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OrderStatus) Descriptor() protoreflect.EnumDescriptor { + return file_loms_v1_loms_proto_enumTypes[0].Descriptor() +} + +func (OrderStatus) Type() protoreflect.EnumType { + return &file_loms_v1_loms_proto_enumTypes[0] +} + +func (x OrderStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use OrderStatus.Descriptor instead. +func (OrderStatus) EnumDescriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{0} +} + +type OrderItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sku int64 `protobuf:"varint,1,opt,name=sku,proto3" json:"sku,omitempty"` + Count uint32 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *OrderItem) Reset() { + *x = OrderItem{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderItem) ProtoMessage() {} + +func (x *OrderItem) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderItem.ProtoReflect.Descriptor instead. +func (*OrderItem) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{0} +} + +func (x *OrderItem) GetSku() int64 { + if x != nil { + return x.Sku + } + return 0 +} + +func (x *OrderItem) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +type OrderCreateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*OrderItem `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *OrderCreateRequest) Reset() { + *x = OrderCreateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderCreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderCreateRequest) ProtoMessage() {} + +func (x *OrderCreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderCreateRequest.ProtoReflect.Descriptor instead. +func (*OrderCreateRequest) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{1} +} + +func (x *OrderCreateRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *OrderCreateRequest) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type OrderCreateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId int64 `protobuf:"varint,1,opt,name=orderId,proto3" json:"orderId,omitempty"` +} + +func (x *OrderCreateResponse) Reset() { + *x = OrderCreateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderCreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderCreateResponse) ProtoMessage() {} + +func (x *OrderCreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderCreateResponse.ProtoReflect.Descriptor instead. +func (*OrderCreateResponse) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{2} +} + +func (x *OrderCreateResponse) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type OrderInfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId int64 `protobuf:"varint,1,opt,name=orderId,proto3" json:"orderId,omitempty"` +} + +func (x *OrderInfoRequest) Reset() { + *x = OrderInfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderInfoRequest) ProtoMessage() {} + +func (x *OrderInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderInfoRequest.ProtoReflect.Descriptor instead. +func (*OrderInfoRequest) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{3} +} + +func (x *OrderInfoRequest) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type OrderInfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Items []*OrderItem `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *OrderInfoResponse) Reset() { + *x = OrderInfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderInfoResponse) ProtoMessage() {} + +func (x *OrderInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderInfoResponse.ProtoReflect.Descriptor instead. +func (*OrderInfoResponse) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{4} +} + +func (x *OrderInfoResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *OrderInfoResponse) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *OrderInfoResponse) GetItems() []*OrderItem { + if x != nil { + return x.Items + } + return nil +} + +type OrderPayRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId int64 `protobuf:"varint,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` +} + +func (x *OrderPayRequest) Reset() { + *x = OrderPayRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderPayRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderPayRequest) ProtoMessage() {} + +func (x *OrderPayRequest) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderPayRequest.ProtoReflect.Descriptor instead. +func (*OrderPayRequest) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{5} +} + +func (x *OrderPayRequest) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type OrderCancelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId int64 `protobuf:"varint,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` +} + +func (x *OrderCancelRequest) Reset() { + *x = OrderCancelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderCancelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderCancelRequest) ProtoMessage() {} + +func (x *OrderCancelRequest) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderCancelRequest.ProtoReflect.Descriptor instead. +func (*OrderCancelRequest) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{6} +} + +func (x *OrderCancelRequest) GetOrderId() int64 { + if x != nil { + return x.OrderId + } + return 0 +} + +type StocksInfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sku int64 `protobuf:"varint,1,opt,name=sku,proto3" json:"sku,omitempty"` +} + +func (x *StocksInfoRequest) Reset() { + *x = StocksInfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StocksInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StocksInfoRequest) ProtoMessage() {} + +func (x *StocksInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StocksInfoRequest.ProtoReflect.Descriptor instead. +func (*StocksInfoRequest) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{7} +} + +func (x *StocksInfoRequest) GetSku() int64 { + if x != nil { + return x.Sku + } + return 0 +} + +type StocksInfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count uint32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *StocksInfoResponse) Reset() { + *x = StocksInfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_loms_v1_loms_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StocksInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StocksInfoResponse) ProtoMessage() {} + +func (x *StocksInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_loms_v1_loms_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StocksInfoResponse.ProtoReflect.Descriptor instead. +func (*StocksInfoResponse) Descriptor() ([]byte, []int) { + return file_loms_v1_loms_proto_rawDescGZIP(), []int{8} +} + +func (x *StocksInfoResponse) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +var File_loms_v1_loms_proto protoreflect.FileDescriptor + +var file_loms_v1_loms_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x6c, 0x6f, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x6f, 0x6d, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x45, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x03, 0x73, 0x6b, 0x75, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x03, 0x73, 0x6b, 0x75, + 0x12, 0x1d, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x62, 0x0a, 0x12, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, + 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, + 0x65, 0x6d, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x35, 0x0a, 0x10, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, + 0x20, 0x00, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x66, 0x0a, 0x11, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x20, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0a, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x22, 0x35, 0x0a, 0x0f, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x50, 0x61, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x20, + 0x00, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x12, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x22, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, 0x07, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x73, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x73, 0x6b, 0x75, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x20, 0x00, 0x52, + 0x03, 0x73, 0x6b, 0x75, 0x22, 0x2a, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x73, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x2a, 0xb1, 0x01, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, + 0x0a, 0x10, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4e, + 0x45, 0x57, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x57, 0x41, 0x49, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x41, + 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x4f, 0x52, 0x44, 0x45, 0x52, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, + 0x12, 0x16, 0x0a, 0x12, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x50, 0x41, 0x59, 0x45, 0x44, 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x4f, 0x52, 0x44, 0x45, + 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, + 0x45, 0x44, 0x10, 0x05, 0x32, 0xc2, 0x04, 0x0a, 0x04, 0x4c, 0x4f, 0x4d, 0x53, 0x12, 0x52, 0x0a, + 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, + 0x0d, 0x2f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x3a, 0x01, + 0x2a, 0x12, 0x47, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x11, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x12, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x08, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x50, 0x61, 0x79, 0x12, 0x10, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x50, 0x61, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x22, 0x0a, 0x2f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2f, 0x70, 0x61, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x54, 0x0a, 0x0b, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x13, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x4a, 0x0a, + 0x0a, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x53, 0x74, + 0x6f, 0x63, 0x6b, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x73, + 0x74, 0x6f, 0x63, 0x6b, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0xad, 0x01, 0x92, 0x41, 0xa9, 0x01, + 0x12, 0x0c, 0x4c, 0x4f, 0x4d, 0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x98, + 0x01, 0x0a, 0x20, 0x46, 0x69, 0x6e, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x6d, 0x6f, 0x72, 0x65, + 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x12, 0x74, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2f, + 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, + 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x61, 0x5a, 0x1d, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x32, 0x35, 0x36, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6c, 0x6f, + 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x6c, 0x6f, 0x6d, 0x73, 0x92, 0x41, 0x3f, 0x12, 0x15, 0x0a, + 0x0c, 0x4c, 0x4f, 0x4d, 0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x32, 0x05, 0x31, + 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_loms_v1_loms_proto_rawDescOnce sync.Once + file_loms_v1_loms_proto_rawDescData = file_loms_v1_loms_proto_rawDesc +) + +func file_loms_v1_loms_proto_rawDescGZIP() []byte { + file_loms_v1_loms_proto_rawDescOnce.Do(func() { + file_loms_v1_loms_proto_rawDescData = protoimpl.X.CompressGZIP(file_loms_v1_loms_proto_rawDescData) + }) + return file_loms_v1_loms_proto_rawDescData +} + +var file_loms_v1_loms_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_loms_v1_loms_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_loms_v1_loms_proto_goTypes = []interface{}{ + (OrderStatus)(0), // 0: OrderStatus + (*OrderItem)(nil), // 1: OrderItem + (*OrderCreateRequest)(nil), // 2: OrderCreateRequest + (*OrderCreateResponse)(nil), // 3: OrderCreateResponse + (*OrderInfoRequest)(nil), // 4: OrderInfoRequest + (*OrderInfoResponse)(nil), // 5: OrderInfoResponse + (*OrderPayRequest)(nil), // 6: OrderPayRequest + (*OrderCancelRequest)(nil), // 7: OrderCancelRequest + (*StocksInfoRequest)(nil), // 8: StocksInfoRequest + (*StocksInfoResponse)(nil), // 9: StocksInfoResponse + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty +} +var file_loms_v1_loms_proto_depIdxs = []int32{ + 1, // 0: OrderCreateRequest.items:type_name -> OrderItem + 1, // 1: OrderInfoResponse.items:type_name -> OrderItem + 2, // 2: LOMS.OrderCreate:input_type -> OrderCreateRequest + 4, // 3: LOMS.OrderInfo:input_type -> OrderInfoRequest + 6, // 4: LOMS.OrderPay:input_type -> OrderPayRequest + 7, // 5: LOMS.OrderCancel:input_type -> OrderCancelRequest + 8, // 6: LOMS.StocksInfo:input_type -> StocksInfoRequest + 3, // 7: LOMS.OrderCreate:output_type -> OrderCreateResponse + 5, // 8: LOMS.OrderInfo:output_type -> OrderInfoResponse + 10, // 9: LOMS.OrderPay:output_type -> google.protobuf.Empty + 10, // 10: LOMS.OrderCancel:output_type -> google.protobuf.Empty + 9, // 11: LOMS.StocksInfo:output_type -> StocksInfoResponse + 7, // [7:12] is the sub-list for method output_type + 2, // [2:7] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_loms_v1_loms_proto_init() } +func file_loms_v1_loms_proto_init() { + if File_loms_v1_loms_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_loms_v1_loms_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderCreateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderCreateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderPayRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderCancelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StocksInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_loms_v1_loms_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StocksInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_loms_v1_loms_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_loms_v1_loms_proto_goTypes, + DependencyIndexes: file_loms_v1_loms_proto_depIdxs, + EnumInfos: file_loms_v1_loms_proto_enumTypes, + MessageInfos: file_loms_v1_loms_proto_msgTypes, + }.Build() + File_loms_v1_loms_proto = out.File + file_loms_v1_loms_proto_rawDesc = nil + file_loms_v1_loms_proto_goTypes = nil + file_loms_v1_loms_proto_depIdxs = nil +} diff --git a/pkg/api/loms/v1/loms.pb.gw.go b/pkg/api/loms/v1/loms.pb.gw.go new file mode 100644 index 0000000..80aacc2 --- /dev/null +++ b/pkg/api/loms/v1/loms.pb.gw.go @@ -0,0 +1,491 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: loms/v1/loms.proto + +/* +Package loms is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package loms + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_LOMS_OrderCreate_0(ctx context.Context, marshaler runtime.Marshaler, client LOMSClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderCreateRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.OrderCreate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LOMS_OrderCreate_0(ctx context.Context, marshaler runtime.Marshaler, server LOMSServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderCreateRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.OrderCreate(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_LOMS_OrderInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_LOMS_OrderInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LOMSClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderInfoRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LOMS_OrderInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.OrderInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LOMS_OrderInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LOMSServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderInfoRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LOMS_OrderInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.OrderInfo(ctx, &protoReq) + return msg, metadata, err + +} + +func request_LOMS_OrderPay_0(ctx context.Context, marshaler runtime.Marshaler, client LOMSClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderPayRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.OrderPay(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LOMS_OrderPay_0(ctx context.Context, marshaler runtime.Marshaler, server LOMSServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderPayRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.OrderPay(ctx, &protoReq) + return msg, metadata, err + +} + +func request_LOMS_OrderCancel_0(ctx context.Context, marshaler runtime.Marshaler, client LOMSClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderCancelRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.OrderCancel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LOMS_OrderCancel_0(ctx context.Context, marshaler runtime.Marshaler, server LOMSServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq OrderCancelRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.OrderCancel(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_LOMS_StocksInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_LOMS_StocksInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LOMSClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StocksInfoRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LOMS_StocksInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.StocksInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LOMS_StocksInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LOMSServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StocksInfoRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LOMS_StocksInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.StocksInfo(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterLOMSHandlerServer registers the http handlers for service LOMS to "mux". +// UnaryRPC :call LOMSServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterLOMSHandlerFromEndpoint instead. +func RegisterLOMSHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LOMSServer) error { + + mux.Handle("POST", pattern_LOMS_OrderCreate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/.LOMS/OrderCreate", runtime.WithHTTPPathPattern("/order/create")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LOMS_OrderCreate_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderCreate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_LOMS_OrderInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/.LOMS/OrderInfo", runtime.WithHTTPPathPattern("/order/info")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LOMS_OrderInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_LOMS_OrderPay_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/.LOMS/OrderPay", runtime.WithHTTPPathPattern("/order/pay")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LOMS_OrderPay_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderPay_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_LOMS_OrderCancel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/.LOMS/OrderCancel", runtime.WithHTTPPathPattern("/order/cancel")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LOMS_OrderCancel_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderCancel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_LOMS_StocksInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/.LOMS/StocksInfo", runtime.WithHTTPPathPattern("/stock/info")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LOMS_StocksInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_StocksInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterLOMSHandlerFromEndpoint is same as RegisterLOMSHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterLOMSHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterLOMSHandler(ctx, mux, conn) +} + +// RegisterLOMSHandler registers the http handlers for service LOMS to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterLOMSHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterLOMSHandlerClient(ctx, mux, NewLOMSClient(conn)) +} + +// RegisterLOMSHandlerClient registers the http handlers for service LOMS +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "LOMSClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LOMSClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "LOMSClient" to call the correct interceptors. +func RegisterLOMSHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LOMSClient) error { + + mux.Handle("POST", pattern_LOMS_OrderCreate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/.LOMS/OrderCreate", runtime.WithHTTPPathPattern("/order/create")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LOMS_OrderCreate_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderCreate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_LOMS_OrderInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/.LOMS/OrderInfo", runtime.WithHTTPPathPattern("/order/info")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LOMS_OrderInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_LOMS_OrderPay_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/.LOMS/OrderPay", runtime.WithHTTPPathPattern("/order/pay")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LOMS_OrderPay_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderPay_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_LOMS_OrderCancel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/.LOMS/OrderCancel", runtime.WithHTTPPathPattern("/order/cancel")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LOMS_OrderCancel_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_OrderCancel_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_LOMS_StocksInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/.LOMS/StocksInfo", runtime.WithHTTPPathPattern("/stock/info")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LOMS_StocksInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LOMS_StocksInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_LOMS_OrderCreate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"order", "create"}, "")) + + pattern_LOMS_OrderInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"order", "info"}, "")) + + pattern_LOMS_OrderPay_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"order", "pay"}, "")) + + pattern_LOMS_OrderCancel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"order", "cancel"}, "")) + + pattern_LOMS_StocksInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"stock", "info"}, "")) +) + +var ( + forward_LOMS_OrderCreate_0 = runtime.ForwardResponseMessage + + forward_LOMS_OrderInfo_0 = runtime.ForwardResponseMessage + + forward_LOMS_OrderPay_0 = runtime.ForwardResponseMessage + + forward_LOMS_OrderCancel_0 = runtime.ForwardResponseMessage + + forward_LOMS_StocksInfo_0 = runtime.ForwardResponseMessage +) diff --git a/pkg/api/loms/v1/loms.pb.validate.go b/pkg/api/loms/v1/loms.pb.validate.go new file mode 100644 index 0000000..3dbf72e --- /dev/null +++ b/pkg/api/loms/v1/loms.pb.validate.go @@ -0,0 +1,1111 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: loms/v1/loms.proto + +package loms + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on OrderItem with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *OrderItem) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderItem with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in OrderItemMultiError, or nil +// if none found. +func (m *OrderItem) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderItem) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetSku() <= 0 { + err := OrderItemValidationError{ + field: "Sku", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if m.GetCount() <= 0 { + err := OrderItemValidationError{ + field: "Count", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return OrderItemMultiError(errors) + } + + return nil +} + +// OrderItemMultiError is an error wrapping multiple validation errors returned +// by OrderItem.ValidateAll() if the designated constraints aren't met. +type OrderItemMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderItemMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderItemMultiError) AllErrors() []error { return m } + +// OrderItemValidationError is the validation error returned by +// OrderItem.Validate if the designated constraints aren't met. +type OrderItemValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderItemValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderItemValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderItemValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderItemValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderItemValidationError) ErrorName() string { return "OrderItemValidationError" } + +// Error satisfies the builtin error interface +func (e OrderItemValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderItem.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderItemValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderItemValidationError{} + +// Validate checks the field values on OrderCreateRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *OrderCreateRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderCreateRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderCreateRequestMultiError, or nil if none found. +func (m *OrderCreateRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderCreateRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetUserId() <= 0 { + err := OrderCreateRequestValidationError{ + field: "UserId", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(m.GetItems()) < 1 { + err := OrderCreateRequestValidationError{ + field: "Items", + reason: "value must contain at least 1 item(s)", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetItems() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, OrderCreateRequestValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, OrderCreateRequestValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return OrderCreateRequestValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return OrderCreateRequestMultiError(errors) + } + + return nil +} + +// OrderCreateRequestMultiError is an error wrapping multiple validation errors +// returned by OrderCreateRequest.ValidateAll() if the designated constraints +// aren't met. +type OrderCreateRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderCreateRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderCreateRequestMultiError) AllErrors() []error { return m } + +// OrderCreateRequestValidationError is the validation error returned by +// OrderCreateRequest.Validate if the designated constraints aren't met. +type OrderCreateRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderCreateRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderCreateRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderCreateRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderCreateRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderCreateRequestValidationError) ErrorName() string { + return "OrderCreateRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e OrderCreateRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderCreateRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderCreateRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderCreateRequestValidationError{} + +// Validate checks the field values on OrderCreateResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *OrderCreateResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderCreateResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderCreateResponseMultiError, or nil if none found. +func (m *OrderCreateResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderCreateResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for OrderId + + if len(errors) > 0 { + return OrderCreateResponseMultiError(errors) + } + + return nil +} + +// OrderCreateResponseMultiError is an error wrapping multiple validation +// errors returned by OrderCreateResponse.ValidateAll() if the designated +// constraints aren't met. +type OrderCreateResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderCreateResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderCreateResponseMultiError) AllErrors() []error { return m } + +// OrderCreateResponseValidationError is the validation error returned by +// OrderCreateResponse.Validate if the designated constraints aren't met. +type OrderCreateResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderCreateResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderCreateResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderCreateResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderCreateResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderCreateResponseValidationError) ErrorName() string { + return "OrderCreateResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e OrderCreateResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderCreateResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderCreateResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderCreateResponseValidationError{} + +// Validate checks the field values on OrderInfoRequest with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *OrderInfoRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderInfoRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderInfoRequestMultiError, or nil if none found. +func (m *OrderInfoRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderInfoRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetOrderId() <= 0 { + err := OrderInfoRequestValidationError{ + field: "OrderId", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return OrderInfoRequestMultiError(errors) + } + + return nil +} + +// OrderInfoRequestMultiError is an error wrapping multiple validation errors +// returned by OrderInfoRequest.ValidateAll() if the designated constraints +// aren't met. +type OrderInfoRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderInfoRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderInfoRequestMultiError) AllErrors() []error { return m } + +// OrderInfoRequestValidationError is the validation error returned by +// OrderInfoRequest.Validate if the designated constraints aren't met. +type OrderInfoRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderInfoRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderInfoRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderInfoRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderInfoRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderInfoRequestValidationError) ErrorName() string { return "OrderInfoRequestValidationError" } + +// Error satisfies the builtin error interface +func (e OrderInfoRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderInfoRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderInfoRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderInfoRequestValidationError{} + +// Validate checks the field values on OrderInfoResponse with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *OrderInfoResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderInfoResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderInfoResponseMultiError, or nil if none found. +func (m *OrderInfoResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderInfoResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Status + + // no validation rules for UserId + + for idx, item := range m.GetItems() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, OrderInfoResponseValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, OrderInfoResponseValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return OrderInfoResponseValidationError{ + field: fmt.Sprintf("Items[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return OrderInfoResponseMultiError(errors) + } + + return nil +} + +// OrderInfoResponseMultiError is an error wrapping multiple validation errors +// returned by OrderInfoResponse.ValidateAll() if the designated constraints +// aren't met. +type OrderInfoResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderInfoResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderInfoResponseMultiError) AllErrors() []error { return m } + +// OrderInfoResponseValidationError is the validation error returned by +// OrderInfoResponse.Validate if the designated constraints aren't met. +type OrderInfoResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderInfoResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderInfoResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderInfoResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderInfoResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderInfoResponseValidationError) ErrorName() string { + return "OrderInfoResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e OrderInfoResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderInfoResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderInfoResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderInfoResponseValidationError{} + +// Validate checks the field values on OrderPayRequest with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *OrderPayRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderPayRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderPayRequestMultiError, or nil if none found. +func (m *OrderPayRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderPayRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetOrderId() <= 0 { + err := OrderPayRequestValidationError{ + field: "OrderId", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return OrderPayRequestMultiError(errors) + } + + return nil +} + +// OrderPayRequestMultiError is an error wrapping multiple validation errors +// returned by OrderPayRequest.ValidateAll() if the designated constraints +// aren't met. +type OrderPayRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderPayRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderPayRequestMultiError) AllErrors() []error { return m } + +// OrderPayRequestValidationError is the validation error returned by +// OrderPayRequest.Validate if the designated constraints aren't met. +type OrderPayRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderPayRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderPayRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderPayRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderPayRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderPayRequestValidationError) ErrorName() string { return "OrderPayRequestValidationError" } + +// Error satisfies the builtin error interface +func (e OrderPayRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderPayRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderPayRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderPayRequestValidationError{} + +// Validate checks the field values on OrderCancelRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *OrderCancelRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on OrderCancelRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// OrderCancelRequestMultiError, or nil if none found. +func (m *OrderCancelRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *OrderCancelRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetOrderId() <= 0 { + err := OrderCancelRequestValidationError{ + field: "OrderId", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return OrderCancelRequestMultiError(errors) + } + + return nil +} + +// OrderCancelRequestMultiError is an error wrapping multiple validation errors +// returned by OrderCancelRequest.ValidateAll() if the designated constraints +// aren't met. +type OrderCancelRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m OrderCancelRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m OrderCancelRequestMultiError) AllErrors() []error { return m } + +// OrderCancelRequestValidationError is the validation error returned by +// OrderCancelRequest.Validate if the designated constraints aren't met. +type OrderCancelRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e OrderCancelRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e OrderCancelRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e OrderCancelRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e OrderCancelRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e OrderCancelRequestValidationError) ErrorName() string { + return "OrderCancelRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e OrderCancelRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sOrderCancelRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = OrderCancelRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = OrderCancelRequestValidationError{} + +// Validate checks the field values on StocksInfoRequest with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *StocksInfoRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on StocksInfoRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// StocksInfoRequestMultiError, or nil if none found. +func (m *StocksInfoRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *StocksInfoRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + if m.GetSku() <= 0 { + err := StocksInfoRequestValidationError{ + field: "Sku", + reason: "value must be greater than 0", + } + if !all { + return err + } + errors = append(errors, err) + } + + if len(errors) > 0 { + return StocksInfoRequestMultiError(errors) + } + + return nil +} + +// StocksInfoRequestMultiError is an error wrapping multiple validation errors +// returned by StocksInfoRequest.ValidateAll() if the designated constraints +// aren't met. +type StocksInfoRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m StocksInfoRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m StocksInfoRequestMultiError) AllErrors() []error { return m } + +// StocksInfoRequestValidationError is the validation error returned by +// StocksInfoRequest.Validate if the designated constraints aren't met. +type StocksInfoRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e StocksInfoRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e StocksInfoRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e StocksInfoRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e StocksInfoRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e StocksInfoRequestValidationError) ErrorName() string { + return "StocksInfoRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e StocksInfoRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sStocksInfoRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = StocksInfoRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = StocksInfoRequestValidationError{} + +// Validate checks the field values on StocksInfoResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *StocksInfoResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on StocksInfoResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// StocksInfoResponseMultiError, or nil if none found. +func (m *StocksInfoResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *StocksInfoResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Count + + if len(errors) > 0 { + return StocksInfoResponseMultiError(errors) + } + + return nil +} + +// StocksInfoResponseMultiError is an error wrapping multiple validation errors +// returned by StocksInfoResponse.ValidateAll() if the designated constraints +// aren't met. +type StocksInfoResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m StocksInfoResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m StocksInfoResponseMultiError) AllErrors() []error { return m } + +// StocksInfoResponseValidationError is the validation error returned by +// StocksInfoResponse.Validate if the designated constraints aren't met. +type StocksInfoResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e StocksInfoResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e StocksInfoResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e StocksInfoResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e StocksInfoResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e StocksInfoResponseValidationError) ErrorName() string { + return "StocksInfoResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e StocksInfoResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sStocksInfoResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = StocksInfoResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = StocksInfoResponseValidationError{} diff --git a/pkg/api/loms/v1/loms_grpc.pb.go b/pkg/api/loms/v1/loms_grpc.pb.go new file mode 100644 index 0000000..5ca67d3 --- /dev/null +++ b/pkg/api/loms/v1/loms_grpc.pb.go @@ -0,0 +1,250 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v6.30.0 +// source: loms/v1/loms.proto + +package loms + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// LOMSClient is the client API for LOMS service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type LOMSClient interface { + OrderCreate(ctx context.Context, in *OrderCreateRequest, opts ...grpc.CallOption) (*OrderCreateResponse, error) + OrderInfo(ctx context.Context, in *OrderInfoRequest, opts ...grpc.CallOption) (*OrderInfoResponse, error) + OrderPay(ctx context.Context, in *OrderPayRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + OrderCancel(ctx context.Context, in *OrderCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + StocksInfo(ctx context.Context, in *StocksInfoRequest, opts ...grpc.CallOption) (*StocksInfoResponse, error) +} + +type lOMSClient struct { + cc grpc.ClientConnInterface +} + +func NewLOMSClient(cc grpc.ClientConnInterface) LOMSClient { + return &lOMSClient{cc} +} + +func (c *lOMSClient) OrderCreate(ctx context.Context, in *OrderCreateRequest, opts ...grpc.CallOption) (*OrderCreateResponse, error) { + out := new(OrderCreateResponse) + err := c.cc.Invoke(ctx, "/LOMS/OrderCreate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *lOMSClient) OrderInfo(ctx context.Context, in *OrderInfoRequest, opts ...grpc.CallOption) (*OrderInfoResponse, error) { + out := new(OrderInfoResponse) + err := c.cc.Invoke(ctx, "/LOMS/OrderInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *lOMSClient) OrderPay(ctx context.Context, in *OrderPayRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/LOMS/OrderPay", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *lOMSClient) OrderCancel(ctx context.Context, in *OrderCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/LOMS/OrderCancel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *lOMSClient) StocksInfo(ctx context.Context, in *StocksInfoRequest, opts ...grpc.CallOption) (*StocksInfoResponse, error) { + out := new(StocksInfoResponse) + err := c.cc.Invoke(ctx, "/LOMS/StocksInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LOMSServer is the server API for LOMS service. +// All implementations must embed UnimplementedLOMSServer +// for forward compatibility +type LOMSServer interface { + OrderCreate(context.Context, *OrderCreateRequest) (*OrderCreateResponse, error) + OrderInfo(context.Context, *OrderInfoRequest) (*OrderInfoResponse, error) + OrderPay(context.Context, *OrderPayRequest) (*emptypb.Empty, error) + OrderCancel(context.Context, *OrderCancelRequest) (*emptypb.Empty, error) + StocksInfo(context.Context, *StocksInfoRequest) (*StocksInfoResponse, error) + mustEmbedUnimplementedLOMSServer() +} + +// UnimplementedLOMSServer must be embedded to have forward compatible implementations. +type UnimplementedLOMSServer struct { +} + +func (UnimplementedLOMSServer) OrderCreate(context.Context, *OrderCreateRequest) (*OrderCreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OrderCreate not implemented") +} +func (UnimplementedLOMSServer) OrderInfo(context.Context, *OrderInfoRequest) (*OrderInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OrderInfo not implemented") +} +func (UnimplementedLOMSServer) OrderPay(context.Context, *OrderPayRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method OrderPay not implemented") +} +func (UnimplementedLOMSServer) OrderCancel(context.Context, *OrderCancelRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method OrderCancel not implemented") +} +func (UnimplementedLOMSServer) StocksInfo(context.Context, *StocksInfoRequest) (*StocksInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StocksInfo not implemented") +} +func (UnimplementedLOMSServer) mustEmbedUnimplementedLOMSServer() {} + +// UnsafeLOMSServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LOMSServer will +// result in compilation errors. +type UnsafeLOMSServer interface { + mustEmbedUnimplementedLOMSServer() +} + +func RegisterLOMSServer(s grpc.ServiceRegistrar, srv LOMSServer) { + s.RegisterService(&LOMS_ServiceDesc, srv) +} + +func _LOMS_OrderCreate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderCreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LOMSServer).OrderCreate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LOMS/OrderCreate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LOMSServer).OrderCreate(ctx, req.(*OrderCreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LOMS_OrderInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LOMSServer).OrderInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LOMS/OrderInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LOMSServer).OrderInfo(ctx, req.(*OrderInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LOMS_OrderPay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderPayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LOMSServer).OrderPay(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LOMS/OrderPay", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LOMSServer).OrderPay(ctx, req.(*OrderPayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LOMS_OrderCancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderCancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LOMSServer).OrderCancel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LOMS/OrderCancel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LOMSServer).OrderCancel(ctx, req.(*OrderCancelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LOMS_StocksInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StocksInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LOMSServer).StocksInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LOMS/StocksInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LOMSServer).StocksInfo(ctx, req.(*StocksInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// LOMS_ServiceDesc is the grpc.ServiceDesc for LOMS service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LOMS_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "LOMS", + HandlerType: (*LOMSServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "OrderCreate", + Handler: _LOMS_OrderCreate_Handler, + }, + { + MethodName: "OrderInfo", + Handler: _LOMS_OrderInfo_Handler, + }, + { + MethodName: "OrderPay", + Handler: _LOMS_OrderPay_Handler, + }, + { + MethodName: "OrderCancel", + Handler: _LOMS_OrderCancel_Handler, + }, + { + MethodName: "StocksInfo", + Handler: _LOMS_StocksInfo_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "loms/v1/loms.proto", +} diff --git a/pkg/go.mod b/pkg/go.mod new file mode 100644 index 0000000..822152c --- /dev/null +++ b/pkg/go.mod @@ -0,0 +1,18 @@ +module route256/pkg + +go 1.23.9 + +require ( + github.com/envoyproxy/protoc-gen-validate v1.2.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 + google.golang.org/grpc v1.73.0 + google.golang.org/protobuf v1.36.6 +) + +require ( + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect +) diff --git a/pkg/go.sum b/pkg/go.sum new file mode 100644 index 0000000..7a5667e --- /dev/null +++ b/pkg/go.sum @@ -0,0 +1,40 @@ +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=