[hw-3] loms service

This commit is contained in:
Никита Шубин
2025-06-20 10:11:59 +00:00
parent c8e056bc99
commit b88dfe6db5
73 changed files with 8837 additions and 52 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
.vscode
bin
.coverage
allure-results
allure-results
vendor-proto/*

View File

@@ -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

125
api/loms/v1/loms.proto Normal file
View File

@@ -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;
}

View File

@@ -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"
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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)

View File

@@ -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")

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
package service
package products
import (
"context"

View File

@@ -1,7 +1,7 @@
package entity
type (
UID uint64
UID int64
Sku int64
)

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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/<user_id>/cart/<sku_id>

View File

@@ -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"

View File

@@ -16,3 +16,4 @@
1. [Основы Go](./homework-1)
2. [Тестирование в Go](./homework-2)
3. [Межсервисное взаимодействие и основы эксплуатации](./homework-3)

262
docs/homework-3/README.md Normal file
View File

@@ -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/<user_id>
Требуется добавить метод 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> (user_id - int64)
```
Response
```
{
order_id int64
}
```
### POST /user/<user_id>/cart/<sku_id>
Требуется добавить запрос в метод добавления товаров в корзину на проверку наличия стоков с помощью вызова gRPC метода loms.StocksInfo.
**Параметры ошибочных ответов:**
Сценарии из прошлых домашних заданий без изменений.
| Сценарий | HTTP код ошибки | Описание |
|-----------------------------------------|-----------------|--------------------------------------------------------------------|
| Превышение стоков при добавлении товара | 412 | Невозможно добавить товара по количеству больше, чем есть в стоках |
| Все остальные случаи | 500 | Проблемы из-за неисправностей в системе |
![cart-cart-item-add](img/cart-cart-item-add.png)
# Путь покупки товаров:
- <cart_host>/user/{user_id}/cart/{sku_id} - Добавляем товар в корзину с проверкой на наличие стоков.
- <cart_host>/user/{user_id}/cart || <cart_host>/user/{user_id}/cart/{sku_id} - Можем удалять товары из корзины.
- <cart_host>/user/{user_id}/cart - Можем получить состав корзины.
- <cart_host>/checkout/{user_id} - Создаем заказ по товарам из корзины.
- <order_host>/order/pay with body { "orderId": {order_id} } - Оплачиваем заказ.
- <order_host>/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 (проверка)

69
docs/homework-3/cart.http Normal file
View File

@@ -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) {}

View File

@@ -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/<user_id>
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -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/<user_id>/cart/<sku_id>\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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,22 @@
@startuml
actor User as u
collections LOMS as l
database OrdersStorage as os
database StocksStorage as ss
u -> 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,17 @@
@startuml
actor User as u
collections LOMS as l
database OrdersStorage as os
database StocksStorage as ss
u -> 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,15 @@
@startuml
actor User as u
collections LOMS as l
database StocksStorage as ss
u -> 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

189
docs/homework-3/loms.grpc Normal file
View File

@@ -0,0 +1,189 @@
### create normal order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCreate <<EOM
{
"userId": 31337,
"items": [
{
"sku": 1076963,
"count": 3
},
{
"sku": 135717466,
"count": 2
}
]
}
EOM
### expected: {"orderId":1}
### get info, assert status="awaiting payment"
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderInfo <<EOM
{
"orderId": 1
}
EOM
### expected: {"status":"awaiting payment","userId":31337,"Items":[{"sku":1076963,"count":3},{"sku":135717466,"count":2}]}
### pay order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderPay <<EOM
{
"orderId": 1
}
EOM
### expected: {}
### check actual status is "payed"
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderInfo <<EOM
{
"orderId": 1
}
EOM
### expected: {"status":"payed","userId":31337,"Items":[{"sku":1076963,"count":3},{"sku":135717466,"count":2}]}
### unable to cancel payed order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCancel <<EOM
{
"orderId": 1
}
EOM
### expected: Code: FailedPrecondition ...
### get unknown order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderInfo <<EOM
{
"orderId": 404
}
EOM
### expected: Code: NotFound
### cancel order not exists
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCancel <<EOM
{
"orderId": 404
}
EOM
### expected: Code: NotFound
### create order with item that has no stocks info
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCreate <<EOM
{
"userId": 31337,
"items": [
{
"sku": 404,
"count": 3
}
]
}
EOM
### expected: Code: FailedPrecondition
### check order status is failed (not necessary, because no orderId after creation if any fails)
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderInfo <<EOM
{
"orderId": 2
}
EOM
### expected: {"status":"failed","userId":31337,"Items":[{"sku":404,"count":3}]}
### cancel failed order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCancel <<EOM
{
"orderId": 2
}
EOM
### expected: Code: FailedPrecondition
### stock info for unknown sku
grpcurl -plaintext -d @ localhost:8083 loms.Loms.StocksInfo <<EOM
{
"sku": 404
}
EOM
### expected: Code: NotFound
### stock info for normal sku
grpcurl -plaintext -d @ localhost:8083 loms.Loms.StocksInfo <<EOM
{
"sku": 135717466
}
EOM
### expected: {"count":78}
### create order with count for sku more than stock
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCreate <<EOM
{
"userId": 31337,
"items": [
{
"sku": 135717466,
"count": 79
}
]
}
EOM
### expected: Code: FailedPrecondition
### no change in stock info after failed order creation
grpcurl -plaintext -d @ localhost:8083 loms.Loms.StocksInfo <<EOM
{
"sku": 135717466
}
EOM
### expected: {"count":500}; 200 OK
### create normal order for cancellation
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCreate <<EOM
{
"userId": 31337,
"items": [
{
"sku": 135717466,
"count": 2
}
]
}
EOM
### expected: {"orderId":4}
### cancel order
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderCancel <<EOM
{
"orderId": 4
}
EOM
### expected: {}
### check canceled order status
grpcurl -plaintext -d @ localhost:8083 loms.Loms.OrderInfo <<EOM
{
"orderId": 4
}
EOM
### expected: {"status":"cancelled","userId":31337,"Items":[{"sku":1076963,"count":2}]}
### check stocks returns
grpcurl -plaintext -d @ localhost:8083 loms.Loms.StocksInfo <<EOM
{
"sku": 135717466
}
EOM
### expected: {"count":78}

176
docs/homework-3/loms.http Normal file
View File

@@ -0,0 +1,176 @@
### create normal order
POST http://localhost:8084/order/create
Content-Type: application/json
{
"userId": 31337,
"items": [
{
"sku": 1076963,
"count": 3
},
{
"sku": 135717466,
"count": 2
}
]
}
### expected: 200 (OK) {"orderId":1}
### get info, assert status="awaiting payment"
GET http://localhost:8084/order/info?orderId=1
Content-Type: application/json
### expected: 200 (OK) {"status":"awaiting payment","user":31337,"Items":[{"sku":1076963,"count":3},{"sku":135717466,"count":2}]}
### pay order
POST http://localhost:8084/order/pay
Content-Type: application/json
{
"orderId": 1
}
### expected: 200 (OK) {}
### check actual status is "payed"
GET http://localhost:8084/order/info?orderId=1
Content-Type: application/json
### expected: 200 (OK) {"status":"payed","user":31337,"Items":[{"sku":1076963,"count":3},{"sku":135717466,"count":2}]}
### unable to cancel payed order
POST http://localhost:8084/order/cancel
Content-Type: application/json
{
"orderId": 1
}
### expected: 400 (Bad Request) {"code":9, ... }
### get unknown order
GET http://localhost:8084/order/info?orderId=404
Content-Type: application/json
### expected: 404 (Not Found) {"code": 5, ... }
### cancel order not exists
POST http://localhost:8084/order/cancel
Content-Type: application/json
{
"orderId": 404
}
### expected: 404 (Not Found) {"code": 5, ... }
### create order with item that has no stocks info
POST http://localhost:8084/order/create
Content-Type: application/json
{
"userId": 31337,
"items": [
{
"sku": 404,
"count": 3
}
]
}
### expected: 400 (Bad Request) {"code":9, ... }
### check order status is failed (not necessary, because no orderId after creation if any fails)
GET http://localhost:8084/order/info?orderId=2
Content-Type: application/json
### expected: 200 (OK) {"status":"failed","userId":31337,"Items":[{"sku":404,"count":3}]}
### cancel failed order
POST http://localhost:8084/order/cancel
Content-Type: application/json
{
"orderId": 2
}
### expected: 400 (Bad Request) {"code":9, ... }
### stock info for unknown sku
GET http://localhost:8084/stock/info?sku=404
Content-Type: application/json
### expected: 404 Not Found {"code":5, ... }
### stock info for normal sku
GET http://localhost:8084/stock/info?sku=135717466
Content-Type: application/json
### expected: 200 (OK) {"count":78}
### create order with count for sku more than stock
POST http://localhost:8084/order/create
Content-Type: application/json
{
"userId": 31337,
"items": [
{
"sku": 135717466,
"count": 79
}
]
}
### expected: 400 (Bad Request) {"code":9, ... }
### no change in stock info after failed order creation
GET http://localhost:8084/stock/info?sku=135717466
Content-Type: application/json
### expected: 200 (OK) {"count":78}
### create normal order for cancellation
POST http://localhost:8084/order/create
Content-Type: application/json
{
"userId": 31337,
"items": [
{
"sku": 135717466,
"count": 2
}
]
}
### expected: 200 (OK) {"orderId":4}
### cancel order
POST http://localhost:8084/order/cancel
Content-Type: application/json
{
"orderId": 4
}
### expected: 200 (OK)
### check canceled order status
GET http://localhost:8084/order/info?orderId=4
Content-Type: application/json
### expected: {"status":"cancelled","user":31337,"Items":[{"sku":1076963,"count":2}]}
### check stocks returns
GET http://localhost:8084/stock/info?sku=135717466
Content-Type: application/json
### expected: {"count":78}; 200 OK

View File

@@ -0,0 +1,37 @@
[
{
"sku": 139275865,
"total_count": 65534,
"reserved": 0
},
{
"sku": 2956315,
"total_count": 100,
"reserved": 30
},
{
"sku": 1076963,
"total_count": 100,
"reserved": 35
},
{
"sku": 135717466,
"total_count": 100,
"reserved": 20
},
{
"sku": 135937324,
"total_count": 100,
"reserved": 30
},
{
"sku": 1625903,
"total_count": 10000,
"reserved": 0
},
{
"sku": 1148162,
"total_count": 100,
"reserved": 0
}
]

View File

@@ -1,8 +1,9 @@
go 1.23.9
use (
./cart
./loms
./notifier
./comments
./cart
./comments
./loms
./notifier
./pkg
)

View File

@@ -1,16 +1,58 @@
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexdigest/gowrap v1.4.2/go.mod h1:s+1hE6qakgdaaLqgdwPAj5qKYVBCSbPJhEbx+I1ef/Q=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=

22
loms/Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM golang:1.23.9-alpine as builder
WORKDIR /build
COPY loms/go.mod go.mod
COPY loms/go.sum go.sum
RUN go mod download
COPY . .
WORKDIR loms
RUN CGO_ENABLED=0 GOOS=linux go build -o /server ./cmd/server/main.go
FROM scratch
COPY --from=builder server /bin/server
COPY loms/configs/values_local.yaml /bin/config/values_local.yaml
ENV CONFIG_FILE=/bin/config/values_local.yaml
ENTRYPOINT ["/bin/server"]

18
loms/cmd/server/main.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"os"
"route256/loms/internal/app"
)
func main() {
srv, err := app.NewApp(os.Getenv("CONFIG_FILE"))
if err != nil {
panic(err)
}
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}

View File

@@ -1,7 +1,8 @@
service:
host: localhost
host: "[::]"
grpc_port: 8083
http_port: 8084
log_level: trace
jaeger:
host: localhost

View File

@@ -1,3 +1,27 @@
module route256/loms
go 1.23.1
require (
github.com/gojuno/minimock/v3 v3.4.5
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.8.4
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
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/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
)

72
loms/go.sum Normal file
View File

@@ -0,0 +1,72 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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.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=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

119
loms/internal/app/app.go Normal file
View File

@@ -0,0 +1,119 @@
package app
import (
"context"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
"route256/loms/internal/app/server"
ordersRepository "route256/loms/internal/domain/repository/orders"
stocksRepository "route256/loms/internal/domain/repository/stocks"
"route256/loms/internal/domain/service"
"route256/loms/internal/infra/config"
mw "route256/loms/internal/infra/grpc/middleware"
pb "route256/pkg/api/loms/v1"
)
type App struct {
config *config.Config
controller *server.Server
}
func NewApp(configPath string) (*App, error) {
c, err := config.LoadConfig(configPath)
if err != nil {
return nil, fmt.Errorf("unable to load config: %w", err)
}
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if c.Service.LogLevel != "" {
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)
}
log.WithLevel(zerolog.GlobalLevel()).Msgf("using logging level=`%s`", zerolog.GlobalLevel().String())
stockRepo, err := stocksRepository.NewInMemoryRepository(100)
if err != nil {
return nil, fmt.Errorf("stocksRepository.NewInMemoryRepository: %w", err)
}
orderRepo := ordersRepository.NewInMemoryRepository(100)
service := service.NewLomsService(orderRepo, stockRepo)
controller := server.NewServer(service)
app := &App{
config: c,
controller: controller,
}
return app, nil
}
func (app *App) ListenAndServe() error {
grpcAddr := fmt.Sprintf("%s:%s", app.config.Service.Host, app.config.Service.GRPCPort)
l, err := net.Listen("tcp", grpcAddr)
if err != nil {
return err
}
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(
mw.Logging,
mw.Validate,
),
)
reflection.Register(grpcServer)
pb.RegisterLOMSServer(grpcServer, app.controller)
go func() {
if err = grpcServer.Serve(l); err != nil {
log.Fatal().Msgf("failed to serve: %v", err)
}
}()
log.Info().Msgf("Serving grpc loms at grpc://%s", l.Addr())
// Setup HTTP gateway
conn, err := grpc.NewClient(
grpcAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return fmt.Errorf("grpc.NewClient: %w", err)
}
gwmux := runtime.NewServeMux()
if err = pb.RegisterLOMSHandler(context.Background(), gwmux, conn); err != nil {
return fmt.Errorf("pb.RegisterLOMSHandler: %w", err)
}
gwServer := &http.Server{
Addr: fmt.Sprintf("%s:%s", app.config.Service.Host, app.config.Service.HTTPPort),
Handler: gwmux,
ReadTimeout: 10 * time.Second,
}
log.Info().Msgf("Serving http loms at http://%s", gwServer.Addr)
return gwServer.ListenAndServe()
}

View File

@@ -0,0 +1,148 @@
package server
import (
"context"
"fmt"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"route256/loms/internal/domain/entity"
"route256/loms/internal/domain/model"
pb "route256/pkg/api/loms/v1"
)
var _ pb.LOMSServer = (*Server)(nil)
type LomsService interface {
OrderCreate(ctx context.Context, orderReq *pb.OrderCreateRequest) (entity.ID, error)
OrderInfo(ctx context.Context, orderID entity.ID) (*entity.Order, error)
OrderPay(ctx context.Context, orderID entity.ID) error
OrderCancel(ctx context.Context, orderID entity.ID) error
StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error)
}
type Server struct {
pb.UnimplementedLOMSServer
service LomsService
}
func NewServer(lomsService LomsService) *Server {
return &Server{
service: lomsService,
}
}
func mapOrderStatus(pbStatus pb.OrderStatus) (string, error) {
switch pbStatus {
case pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT:
return "awaiting payment", nil
case pb.OrderStatus_ORDER_STATUS_CANCELLED:
return "cancelled", nil
case pb.OrderStatus_ORDER_STATUS_FAILED:
return "failed", nil
case pb.OrderStatus_ORDER_STATUS_NEW:
return "new", nil
case pb.OrderStatus_ORDER_STATUS_PAYED:
return "payed", nil
default:
return "", fmt.Errorf("unexpected OrderStatus: %v", pbStatus)
}
}
func (s *Server) OrderCreate(ctx context.Context, req *pb.OrderCreateRequest) (*pb.OrderCreateResponse, error) {
id, err := s.service.OrderCreate(ctx, req)
switch {
case errors.Is(err, model.ErrInvalidInput):
return nil, status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, model.ErrNotEnoughStocks):
return nil, status.Error(codes.FailedPrecondition, err.Error())
case err != nil:
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.OrderCreateResponse{OrderId: int64(id)}, nil
}
func (s *Server) OrderInfo(ctx context.Context, req *pb.OrderInfoRequest) (*pb.OrderInfoResponse, error) {
ord, err := s.service.OrderInfo(ctx, entity.ID(req.OrderId))
switch {
case errors.Is(err, model.ErrInvalidInput):
return nil, status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, model.ErrOrderNotFound):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, status.Error(codes.Internal, err.Error())
}
items := make([]*pb.OrderItem, len(ord.Items))
for i, item := range ord.Items {
items[i] = &pb.OrderItem{
Sku: int64(item.ID),
Count: item.Count,
}
}
orderStatus, err := mapOrderStatus(pb.OrderStatus(pb.OrderStatus_value[ord.Status]))
if err != nil {
return nil, err
}
resp := &pb.OrderInfoResponse{
Status: orderStatus,
UserId: int64(ord.UserID),
Items: items,
}
return resp, nil
}
func (s *Server) OrderPay(ctx context.Context, req *pb.OrderPayRequest) (*emptypb.Empty, error) {
err := s.service.OrderPay(ctx, entity.ID(req.OrderId))
switch {
case errors.Is(err, model.ErrInvalidInput):
return nil, status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, model.ErrOrderNotFound):
return nil, status.Error(codes.NotFound, err.Error())
case errors.Is(err, model.ErrOrderInvalidStatus):
return nil, status.Error(codes.FailedPrecondition, err.Error())
case err != nil:
return nil, status.Error(codes.Internal, err.Error())
}
return &emptypb.Empty{}, nil
}
func (s *Server) OrderCancel(ctx context.Context, req *pb.OrderCancelRequest) (*emptypb.Empty, error) {
err := s.service.OrderCancel(ctx, entity.ID(req.OrderId))
switch {
case errors.Is(err, model.ErrInvalidInput):
return nil, status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, model.ErrOrderNotFound):
return nil, status.Error(codes.NotFound, err.Error())
case errors.Is(err, model.ErrOrderInvalidStatus):
return nil, status.Error(codes.FailedPrecondition, err.Error())
case err != nil:
return nil, status.Error(codes.Internal, err.Error())
}
return &emptypb.Empty{}, nil
}
func (s *Server) StocksInfo(ctx context.Context, req *pb.StocksInfoRequest) (*pb.StocksInfoResponse, error) {
count, err := s.service.StocksInfo(ctx, entity.Sku(req.Sku))
switch {
case errors.Is(err, model.ErrInvalidInput):
return nil, status.Error(codes.InvalidArgument, err.Error())
case errors.Is(err, model.ErrOrderNotFound), errors.Is(err, model.ErrUnknownStock):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.StocksInfoResponse{Count: count}, nil
}

View File

@@ -0,0 +1,18 @@
package entity
type (
ID int64
Sku int64
)
type Order struct {
OrderID ID
Status string
UserID ID
Items []OrderItem
}
type OrderItem struct {
ID Sku
Count uint32
}

View File

@@ -0,0 +1,6 @@
package entity
type Stock struct {
Item OrderItem
Reserved uint32
}

View File

@@ -0,0 +1,13 @@
package model
import "errors"
var (
ErrInvalidInput = errors.New("invalid input")
ErrNotEnoughStocks = errors.New("not enough stocks")
ErrUnknownStock = errors.New("unknown stock provided")
ErrOrderNotFound = errors.New("order not found")
ErrOrderInvalidStatus = errors.New("invalid order status")
)

View File

@@ -0,0 +1,77 @@
package repository
import (
"context"
"sync"
"route256/loms/internal/domain/entity"
"route256/loms/internal/domain/model"
)
type storage = map[entity.ID]*entity.Order
type InMemoryRepository struct {
storage storage
mx sync.RWMutex
idCounter entity.ID
}
func NewInMemoryRepository(cap int) *InMemoryRepository {
return &InMemoryRepository{
storage: make(storage, cap),
}
}
func (r *InMemoryRepository) OrderCreate(_ context.Context, order *entity.Order) (entity.ID, error) {
r.mx.Lock()
defer r.mx.Unlock()
r.idCounter++
orderCopy := &entity.Order{
OrderID: r.idCounter,
Status: order.Status,
UserID: order.UserID,
Items: make([]entity.OrderItem, len(order.Items)),
}
copy(orderCopy.Items, order.Items)
r.storage[orderCopy.OrderID] = orderCopy
return r.idCounter, nil
}
func (r *InMemoryRepository) OrderSetStatus(_ context.Context, orderID entity.ID, newStatus string) error {
r.mx.Lock()
defer r.mx.Unlock()
if _, ok := r.storage[orderID]; !ok {
return model.ErrOrderNotFound
}
r.storage[orderID].Status = newStatus
return nil
}
func (r *InMemoryRepository) OrderGetByID(_ context.Context, orderID entity.ID) (*entity.Order, error) {
r.mx.Lock()
defer r.mx.Unlock()
order, ok := r.storage[orderID]
if !ok {
return nil, model.ErrOrderNotFound
}
orderCopy := &entity.Order{
OrderID: order.OrderID,
Status: order.Status,
UserID: order.UserID,
Items: make([]entity.OrderItem, len(order.Items)),
}
copy(orderCopy.Items, order.Items)
return orderCopy, nil
}

View File

@@ -0,0 +1,114 @@
package repository
import (
"context"
_ "embed"
"encoding/json"
"sync"
"route256/loms/internal/domain/entity"
"route256/loms/internal/domain/model"
)
//go:embed stock-data.json
var stockData []byte
type storage = map[entity.Sku]*entity.Stock
type InMemoryRepository struct {
storage storage
mx sync.RWMutex
}
func NewInMemoryRepository(cap int) (*InMemoryRepository, error) {
var rows []struct {
Sku entity.Sku `json:"sku"`
TotalCount uint32 `json:"total_count"`
Reserved uint32 `json:"reserved"`
}
if err := json.Unmarshal(stockData, &rows); err != nil {
return nil, err
}
repo := &InMemoryRepository{
storage: make(storage, cap),
}
for _, r := range rows {
repo.storage[r.Sku] = &entity.Stock{
Item: entity.OrderItem{
ID: r.Sku,
Count: r.TotalCount,
},
Reserved: r.Reserved,
}
}
return repo, nil
}
func (r *InMemoryRepository) StockReserve(_ context.Context, stock *entity.Stock) error {
r.mx.Lock()
defer r.mx.Unlock()
if _, ok := r.storage[stock.Item.ID]; !ok {
return model.ErrNotEnoughStocks
}
if r.storage[stock.Item.ID].Item.Count < stock.Reserved {
return model.ErrNotEnoughStocks
}
r.storage[stock.Item.ID].Item.Count -= stock.Reserved
r.storage[stock.Item.ID].Reserved += stock.Reserved
return nil
}
func (r *InMemoryRepository) StockReserveRemove(_ context.Context, stock *entity.Stock) error {
r.mx.Lock()
defer r.mx.Unlock()
if _, ok := r.storage[stock.Item.ID]; !ok {
return model.ErrUnknownStock
}
if r.storage[stock.Item.ID].Reserved < stock.Reserved {
return model.ErrNotEnoughStocks
}
r.storage[stock.Item.ID].Reserved -= stock.Reserved
return nil
}
func (r *InMemoryRepository) StockCancel(_ context.Context, stock *entity.Stock) error {
r.mx.Lock()
defer r.mx.Unlock()
if _, ok := r.storage[stock.Item.ID]; !ok {
return model.ErrUnknownStock
}
if r.storage[stock.Item.ID].Reserved < stock.Reserved {
return model.ErrNotEnoughStocks
}
r.storage[stock.Item.ID].Reserved -= stock.Reserved
r.storage[stock.Item.ID].Item.Count += stock.Reserved
return nil
}
func (r *InMemoryRepository) StockGetByID(_ context.Context, sku entity.Sku) (*entity.Stock, error) {
r.mx.Lock()
defer r.mx.Unlock()
stock, ok := r.storage[sku]
if !ok {
return nil, model.ErrUnknownStock
}
return stock, nil
}

View File

@@ -0,0 +1,37 @@
[
{
"sku": 139275865,
"total_count": 65534,
"reserved": 0
},
{
"sku": 2956315,
"total_count": 100,
"reserved": 30
},
{
"sku": 1076963,
"total_count": 100,
"reserved": 35
},
{
"sku": 135717466,
"total_count": 100,
"reserved": 20
},
{
"sku": 135937324,
"total_count": 100,
"reserved": 30
},
{
"sku": 1625903,
"total_count": 10000,
"reserved": 0
},
{
"sku": 1148162,
"total_count": 100,
"reserved": 0
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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))
})
}

View File

@@ -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)

827
pkg/api/loms/v1/loms.pb.go Normal file
View File

@@ -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
}

View File

@@ -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
)

File diff suppressed because it is too large Load Diff

View File

@@ -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",
}

18
pkg/go.mod Normal file
View File

@@ -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
)

40
pkg/go.sum Normal file
View File

@@ -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=