first commit
This commit is contained in:
commit
db01b8564d
6
.env
Normal file
6
.env
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
SERVER_PORT=8080
|
||||||
|
DB_HOST=postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=postgres
|
||||||
|
DB_NAME=postgres
|
||||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go build -mod vendor -o app_api ./main.go
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/app_api .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./app_api"]
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Реализация тестового задания API подписчиков для Effective Mobile на Go
|
||||||
|
|
||||||
|
swagger url `http://<host name or ip>:8080/swagger/index.html`
|
||||||
51
docker-compose.yml
Normal file
51
docker-compose.yml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
ports:
|
||||||
|
- 0.0.0.0:5432:5432
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- effective_mobile_test_go_api
|
||||||
|
|
||||||
|
# migrate:
|
||||||
|
# image: migrate/migrate:v4.15.1
|
||||||
|
# command: -path=/migrations -database postgresql://postgres:postgres@postgres:5432/postgres?sslmode=disable up
|
||||||
|
# volumes:
|
||||||
|
# - ./migrations:/migrations
|
||||||
|
# depends_on:
|
||||||
|
# - postgres
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
depends_on:
|
||||||
|
# - migrate
|
||||||
|
- postgres
|
||||||
|
env_file:
|
||||||
|
./.env.docker
|
||||||
|
ports:
|
||||||
|
- 0.0.0.0:8080:8080
|
||||||
|
# environment:
|
||||||
|
# - SERVER_PORT=8080
|
||||||
|
# - DB_HOST=postgres
|
||||||
|
# - DB_PORT=5432
|
||||||
|
# - DB_USER=postgres
|
||||||
|
# - DB_PASSWORD=postgres
|
||||||
|
# - DB_NAME=effective_mobile_test_go_api
|
||||||
|
volumes:
|
||||||
|
- ./.env.docker:/app/.env
|
||||||
|
- ./migrations:/app/migrations
|
||||||
|
networks:
|
||||||
|
- effective_mobile_test_go_api
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
effective_mobile_test_go_api:
|
||||||
|
driver: bridge
|
||||||
377
docs/docs.go
Normal file
377
docs/docs.go
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import "github.com/swaggo/swag"
|
||||||
|
|
||||||
|
const docTemplate = `{
|
||||||
|
"schemes": {{ marshal .Schemes }},
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "{{escape .Description}}",
|
||||||
|
"title": "{{.Title}}",
|
||||||
|
"contact": {},
|
||||||
|
"version": "{{.Version}}"
|
||||||
|
},
|
||||||
|
"host": "{{.Host}}",
|
||||||
|
"basePath": "{{.BasePath}}",
|
||||||
|
"paths": {
|
||||||
|
"/subscriptions": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Список подписок",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Создать подписку",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Создание подписки",
|
||||||
|
"name": "subscription",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.CreateSubscriptionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/subscriptions/sum": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Суммарная стоимость подписок за период с фильтрацией",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "UUID пользователя",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Название сервиса",
|
||||||
|
"name": "service_name",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "\"07-2025\"",
|
||||||
|
"description": "Начало периода, формат MM-YYYY",
|
||||||
|
"name": "start",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "\"08-2025\"",
|
||||||
|
"description": "Конец периода, формат MM-YYYY",
|
||||||
|
"name": "end",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/subscriptions/{id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Получить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Обновить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Обновление подписки",
|
||||||
|
"name": "subscription",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.CreateSubscriptionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Удалить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"handler.CreateSubscriptionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_date": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "12-2025"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 400
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Yandex Plus"
|
||||||
|
},
|
||||||
|
"start_date": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "07-2025"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "60601fee-2bf1-4721-ae6f-7636e79a0cba"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model.Subscription": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"start_date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0",
|
||||||
|
Host: "",
|
||||||
|
BasePath: "/",
|
||||||
|
Schemes: []string{},
|
||||||
|
Title: "Subscription API",
|
||||||
|
Description: "API для агрегации онлайн-подписок пользователей.",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
SwaggerTemplate: docTemplate,
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
||||||
3
docs/swagger-custom.css
Normal file
3
docs/swagger-custom.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.topbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
352
docs/swagger.json
Normal file
352
docs/swagger.json
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"description": "API для агрегации онлайн-подписок пользователей.",
|
||||||
|
"title": "Subscription API",
|
||||||
|
"contact": {},
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"basePath": "/",
|
||||||
|
"paths": {
|
||||||
|
"/subscriptions": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Список подписок",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Создать подписку",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Создание подписки",
|
||||||
|
"name": "subscription",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.CreateSubscriptionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/subscriptions/sum": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Суммарная стоимость подписок за период с фильтрацией",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "UUID пользователя",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Название сервиса",
|
||||||
|
"name": "service_name",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "\"07-2025\"",
|
||||||
|
"description": "Начало периода, формат MM-YYYY",
|
||||||
|
"name": "start",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"example": "\"08-2025\"",
|
||||||
|
"description": "Конец периода, формат MM-YYYY",
|
||||||
|
"name": "end",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/subscriptions/{id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Получить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Обновить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Обновление подписки",
|
||||||
|
"name": "subscription",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.CreateSubscriptionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Subscription"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"subscriptions"
|
||||||
|
],
|
||||||
|
"summary": "Удалить подписку по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "ID подписки",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"handler.CreateSubscriptionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_date": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "12-2025"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 400
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Yandex Plus"
|
||||||
|
},
|
||||||
|
"start_date": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "07-2025"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "60601fee-2bf1-4721-ae6f-7636e79a0cba"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model.Subscription": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"end_date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"start_date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
233
docs/swagger.yaml
Normal file
233
docs/swagger.yaml
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
basePath: /
|
||||||
|
definitions:
|
||||||
|
handler.CreateSubscriptionRequest:
|
||||||
|
properties:
|
||||||
|
end_date:
|
||||||
|
example: 12-2025
|
||||||
|
type: string
|
||||||
|
price:
|
||||||
|
example: 400
|
||||||
|
type: integer
|
||||||
|
service_name:
|
||||||
|
example: Yandex Plus
|
||||||
|
type: string
|
||||||
|
start_date:
|
||||||
|
example: 07-2025
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
example: 60601fee-2bf1-4721-ae6f-7636e79a0cba
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
model.Subscription:
|
||||||
|
properties:
|
||||||
|
end_date:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
price:
|
||||||
|
type: integer
|
||||||
|
service_name:
|
||||||
|
type: string
|
||||||
|
start_date:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
description: API для агрегации онлайн-подписок пользователей.
|
||||||
|
title: Subscription API
|
||||||
|
version: "1.0"
|
||||||
|
paths:
|
||||||
|
/subscriptions:
|
||||||
|
get:
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/model.Subscription'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Список подписок
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Создание подписки
|
||||||
|
in: body
|
||||||
|
name: subscription
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.CreateSubscriptionRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Subscription'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Создать подписку
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
/subscriptions/{id}:
|
||||||
|
delete:
|
||||||
|
parameters:
|
||||||
|
- description: ID подписки
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Удалить подписку по ID
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: ID подписки
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Subscription'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Получить подписку по ID
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: ID подписки
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Обновление подписки
|
||||||
|
in: body
|
||||||
|
name: subscription
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.CreateSubscriptionRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Subscription'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Обновить подписку по ID
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
/subscriptions/sum:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: UUID пользователя
|
||||||
|
in: query
|
||||||
|
name: user_id
|
||||||
|
type: string
|
||||||
|
- description: Название сервиса
|
||||||
|
in: query
|
||||||
|
name: service_name
|
||||||
|
type: string
|
||||||
|
- description: Начало периода, формат MM-YYYY
|
||||||
|
example: '"07-2025"'
|
||||||
|
in: query
|
||||||
|
name: start
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Конец периода, формат MM-YYYY
|
||||||
|
example: '"08-2025"'
|
||||||
|
in: query
|
||||||
|
name: end
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Суммарная стоимость подписок за период с фильтрацией
|
||||||
|
tags:
|
||||||
|
- subscriptions
|
||||||
|
swagger: "2.0"
|
||||||
45
go.mod
Normal file
45
go.mod
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
module effective_mobile_test_go_api
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/mux v1.7.4
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.6 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/mod v0.26.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
golang.org/x/tools v0.35.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
168
go.sum
Normal file
168
go.sum
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||||
|
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/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/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||||
|
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||||
|
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
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/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||||
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
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/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
31
internal/config/config.go
Normal file
31
internal/config/config.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ServerPort string
|
||||||
|
DBHost string
|
||||||
|
DBPort string
|
||||||
|
DBUser string
|
||||||
|
DBPassword string
|
||||||
|
DBName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() *Config {
|
||||||
|
viper.SetConfigFile(".env")
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
log.Fatalf("Ошибка чтения конфигурации: %v", err)
|
||||||
|
}
|
||||||
|
return &Config{
|
||||||
|
ServerPort: viper.GetString("SERVER_PORT"),
|
||||||
|
DBHost: viper.GetString("DB_HOST"),
|
||||||
|
DBPort: viper.GetString("DB_PORT"),
|
||||||
|
DBUser: viper.GetString("DB_USER"),
|
||||||
|
DBPassword: viper.GetString("DB_PASSWORD"),
|
||||||
|
DBName: viper.GetString("DB_NAME"),
|
||||||
|
}
|
||||||
|
}
|
||||||
252
internal/handler/handler.go
Normal file
252
internal/handler/handler.go
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"effective_mobile_test_go_api/internal/model"
|
||||||
|
"effective_mobile_test_go_api/internal/service"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
log *zap.Logger
|
||||||
|
service *service.SubscriptionService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(log *zap.Logger, service *service.SubscriptionService) *Handler {
|
||||||
|
return &Handler{log: log, service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSubscriptionRequest struct {
|
||||||
|
ServiceName string `json:"service_name" example:"Yandex Plus"`
|
||||||
|
Price int `json:"price" example:"400"`
|
||||||
|
UserID string `json:"user_id" example:"60601fee-2bf1-4721-ae6f-7636e79a0cba"`
|
||||||
|
StartDate string `json:"start_date" example:"07-2025"`
|
||||||
|
EndDate *string `json:"end_date,omitempty" example:"12-2025"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSubscription godoc
|
||||||
|
// @Summary Создать подписку
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param subscription body CreateSubscriptionRequest true "Создание подписки"
|
||||||
|
// @Success 201 {object} model.Subscription
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /subscriptions [post]
|
||||||
|
func (h *Handler) CreateSubscription(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req CreateSubscriptionRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
h.log.Error("Ошибка декодирования", zap.Error(err))
|
||||||
|
http.Error(w, "Ошибка запроса", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userUUID, err := uuid.Parse(req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный uuid пользователя", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startDate, err := time.Parse("01-2006", req.StartDate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный формат start_date", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var endDate *time.Time
|
||||||
|
if req.EndDate != nil {
|
||||||
|
ed, err := time.Parse("01-2006", *req.EndDate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный формат даты окончания", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endDate = &ed
|
||||||
|
}
|
||||||
|
sub := &model.Subscription{
|
||||||
|
ServiceName: req.ServiceName,
|
||||||
|
Price: req.Price,
|
||||||
|
UserID: userUUID,
|
||||||
|
StartDate: startDate,
|
||||||
|
EndDate: endDate,
|
||||||
|
}
|
||||||
|
if err := h.service.Create(r.Context(), sub); err != nil {
|
||||||
|
h.log.Error("Подписчик не создан", zap.Error(err))
|
||||||
|
http.Error(w, "Ошибка создания", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubscription godoc
|
||||||
|
// @Summary Получить подписку по ID
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "ID подписки"
|
||||||
|
// @Success 200 {object} model.Subscription
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Router /subscriptions/{id} [get]
|
||||||
|
func (h *Handler) GetSubscription(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idStr := mux.Vars(r)["id"]
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный идентификатор", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sub, err := h.service.GetByID(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Подписчик не найден", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubscription godoc
|
||||||
|
// @Summary Обновить подписку по ID
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "ID подписки"
|
||||||
|
// @Param subscription body CreateSubscriptionRequest true "Обновление подписки"
|
||||||
|
// @Success 200 {object} model.Subscription
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /subscriptions/{id} [put]
|
||||||
|
func (h *Handler) UpdateSubscription(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idStr := mux.Vars(r)["id"]
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный идентификатор", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req CreateSubscriptionRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Неверный запрос", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userUUID, err := uuid.Parse(req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный uuid пользователя", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startDate, err := time.Parse("01-2006", req.StartDate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный формат даты начала", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var endDate *time.Time
|
||||||
|
if req.EndDate != nil {
|
||||||
|
ed, err := time.Parse("01-2006", *req.EndDate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный формат даты окончания", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endDate = &ed
|
||||||
|
}
|
||||||
|
sub := &model.Subscription{
|
||||||
|
ID: id,
|
||||||
|
ServiceName: req.ServiceName,
|
||||||
|
Price: req.Price,
|
||||||
|
UserID: userUUID,
|
||||||
|
StartDate: startDate,
|
||||||
|
EndDate: endDate,
|
||||||
|
}
|
||||||
|
if err := h.service.Update(r.Context(), sub); err != nil {
|
||||||
|
http.Error(w, "Не удалось обновить", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubscription godoc
|
||||||
|
// @Summary Удалить подписку по ID
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Param id path int true "ID подписки"
|
||||||
|
// @Success 204 "No Content"
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /subscriptions/{id} [delete]
|
||||||
|
func (h *Handler) DeleteSubscription(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idStr := mux.Vars(r)["id"]
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный идентификатор", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.service.Delete(r.Context(), id); err != nil {
|
||||||
|
http.Error(w, "Не удалось удалить", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubscriptions godoc
|
||||||
|
// @Summary Список подписок
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} model.Subscription
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /subscriptions [get]
|
||||||
|
func (h *Handler) ListSubscriptions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
subs, err := h.service.List(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Не удалось включить в список", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(subs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumSubscriptions godoc
|
||||||
|
// @Summary Суммарная стоимость подписок за период с фильтрацией
|
||||||
|
// @Tags subscriptions
|
||||||
|
// @Produce json
|
||||||
|
// @Param user_id query string false "UUID пользователя"
|
||||||
|
// @Param service_name query string false "Название сервиса"
|
||||||
|
// @Param start query string true "Начало периода, формат MM-YYYY" example("07-2025")
|
||||||
|
// @Param end query string true "Конец периода, формат MM-YYYY" example("08-2025")
|
||||||
|
// @Success 200 {object} map[string]int
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /subscriptions/sum [get]
|
||||||
|
func (h *Handler) SumSubscriptions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
q := r.URL.Query()
|
||||||
|
var userID *uuid.UUID
|
||||||
|
if u := q.Get("user_id"); u != "" {
|
||||||
|
id, err := uuid.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверный идентификатор пользователя", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID = &id
|
||||||
|
}
|
||||||
|
serviceName := q.Get("service_name")
|
||||||
|
startStr := q.Get("start")
|
||||||
|
endStr := q.Get("end")
|
||||||
|
if startStr == "" || endStr == "" {
|
||||||
|
http.Error(w, "требуются начальные и конечные параметры запроса", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startDate, err := time.Parse("01-2006", startStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверная дата начала", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endDate, err := time.Parse("01-2006", endStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Неверная дата окончания", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sum, err := h.service.SumPrice(r.Context(), userID, serviceName, startDate, endDate)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Не удалось рассчитать сумму", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := map[string]int{"total_price": sum}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
15
internal/model/subscription.go
Normal file
15
internal/model/subscription.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Subscription struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ServiceName string `json:"service_name"`
|
||||||
|
Price int `json:"price"`
|
||||||
|
UserID uuid.UUID `json:"user_id"`
|
||||||
|
StartDate time.Time `json:"start_date"`
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
}
|
||||||
104
internal/repo/repo.go
Normal file
104
internal/repo/repo.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"effective_mobile_test_go_api/internal/model"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubscriptionRepo struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubscriptionRepo(db *sql.DB) *SubscriptionRepo {
|
||||||
|
return &SubscriptionRepo{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) Create(ctx context.Context, s *model.Subscription) error {
|
||||||
|
query := `INSERT INTO subscriptions (service_name, price, user_id, start_date, end_date) VALUES ($1, $2, $3, $4, $5) RETURNING id`
|
||||||
|
var id int
|
||||||
|
err := r.db.QueryRowContext(ctx, query, s.ServiceName, s.Price, s.UserID, s.StartDate, s.EndDate).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.ID = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) GetByID(ctx context.Context, id int) (*model.Subscription, error) {
|
||||||
|
query := `SELECT id, service_name, price, user_id, start_date, end_date FROM subscriptions WHERE id = $1`
|
||||||
|
s := &model.Subscription{}
|
||||||
|
err := r.db.QueryRowContext(ctx, query, id).Scan(&s.ID, &s.ServiceName, &s.Price, &s.UserID, &s.StartDate, &s.EndDate)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, errors.New("подписка не найдена")
|
||||||
|
}
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) Update(ctx context.Context, s *model.Subscription) error {
|
||||||
|
query := `UPDATE subscriptions SET service_name=$1, price=$2, user_id=$3, start_date=$4, end_date=$5 WHERE id=$6`
|
||||||
|
res, err := r.db.ExecContext(ctx, query, s.ServiceName, s.Price, s.UserID, s.StartDate, s.EndDate, s.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rowsAffected, err := res.RowsAffected()
|
||||||
|
if err != nil || rowsAffected == 0 {
|
||||||
|
return errors.New("подписка не найдена или не обновлена")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) Delete(ctx context.Context, id int) error {
|
||||||
|
query := `DELETE FROM subscriptions WHERE id = $1`
|
||||||
|
res, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rowsAffected, err := res.RowsAffected()
|
||||||
|
if err != nil || rowsAffected == 0 {
|
||||||
|
return errors.New("подписка не найдена или не удалена")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) List(ctx context.Context) ([]*model.Subscription, error) {
|
||||||
|
query := `SELECT id, service_name, price, user_id, start_date, end_date FROM subscriptions`
|
||||||
|
rows, err := r.db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var subscriptions []*model.Subscription
|
||||||
|
for rows.Next() {
|
||||||
|
s := &model.Subscription{}
|
||||||
|
if err := rows.Scan(&s.ID, &s.ServiceName, &s.Price, &s.UserID, &s.StartDate, &s.EndDate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subscriptions = append(subscriptions, s)
|
||||||
|
}
|
||||||
|
return subscriptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SubscriptionRepo) SumPrice(ctx context.Context, userID *uuid.UUID, serviceName string, startPeriod, endPeriod time.Time) (int, error) {
|
||||||
|
baseQuery := `SELECT COALESCE(SUM(price), 0) FROM subscriptions WHERE start_date >= $1 AND start_date <= $2`
|
||||||
|
args := []interface{}{startPeriod, endPeriod}
|
||||||
|
i := 3
|
||||||
|
if userID != nil {
|
||||||
|
baseQuery += ` AND user_id = $` + strconv.Itoa(i)
|
||||||
|
args = append(args, *userID)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if serviceName != "" {
|
||||||
|
baseQuery += ` AND service_name = $` + strconv.Itoa(i)
|
||||||
|
args = append(args, serviceName)
|
||||||
|
}
|
||||||
|
var sum int
|
||||||
|
err := r.db.QueryRowContext(ctx, baseQuery, args...).Scan(&sum)
|
||||||
|
return sum, err
|
||||||
|
}
|
||||||
43
internal/service/service.go
Normal file
43
internal/service/service.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"effective_mobile_test_go_api/internal/model"
|
||||||
|
"effective_mobile_test_go_api/internal/repo"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubscriptionService struct {
|
||||||
|
repo *repo.SubscriptionRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubscriptionService(r *repo.SubscriptionRepo) *SubscriptionService {
|
||||||
|
return &SubscriptionService{repo: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) Create(ctx context.Context, sub *model.Subscription) error {
|
||||||
|
return s.repo.Create(ctx, sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) GetByID(ctx context.Context, id int) (*model.Subscription, error) {
|
||||||
|
return s.repo.GetByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) Update(ctx context.Context, sub *model.Subscription) error {
|
||||||
|
return s.repo.Update(ctx, sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) Delete(ctx context.Context, id int) error {
|
||||||
|
return s.repo.Delete(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) List(ctx context.Context) ([]*model.Subscription, error) {
|
||||||
|
return s.repo.List(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubscriptionService) SumPrice(ctx context.Context, userID *uuid.UUID, serviceName string, startPeriod, endPeriod time.Time) (int, error) {
|
||||||
|
return s.repo.SumPrice(ctx, userID, serviceName, startPeriod, endPeriod)
|
||||||
|
}
|
||||||
84
main.go
Normal file
84
main.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// @title Effective Mobile Test Go API
|
||||||
|
// @version 1.0
|
||||||
|
// @description API для агрегации онлайн-подписок пользователей.
|
||||||
|
// @BasePath /
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
_ "effective_mobile_test_go_api/docs"
|
||||||
|
"effective_mobile_test_go_api/internal/config"
|
||||||
|
"effective_mobile_test_go_api/internal/handler"
|
||||||
|
"effective_mobile_test_go_api/internal/repo"
|
||||||
|
"effective_mobile_test_go_api/internal/service"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
|
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runMigrations(db *sql.DB) error {
|
||||||
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := migrate.NewWithDatabaseInstance("file://migrations", "postgres", driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.Up()
|
||||||
|
if err != nil && err != migrate.ErrNoChange {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logConfig := zap.NewProductionConfig()
|
||||||
|
logger, err := logConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Не удалось инициализировать zap logger: %v", err)
|
||||||
|
}
|
||||||
|
defer logger.Sync()
|
||||||
|
cfg := config.LoadConfig()
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", cfg.DBHost, cfg.DBPort, cfg.DBUser, cfg.DBPassword, cfg.DBName)
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Не удалось подключится к СУБД", zap.Error(err))
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
err = db.PingContext(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Недоступна СУБД", zap.Error(err))
|
||||||
|
}
|
||||||
|
err = runMigrations(db)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Миграция не выполнена", zap.Error(err))
|
||||||
|
}
|
||||||
|
subRepo := repo.NewSubscriptionRepo(db)
|
||||||
|
subService := service.NewSubscriptionService(subRepo)
|
||||||
|
h := handler.NewHandler(logger, subService)
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/subscriptions", h.CreateSubscription).Methods("POST")
|
||||||
|
r.HandleFunc("/subscriptions/{id}", h.GetSubscription).Methods("GET")
|
||||||
|
r.HandleFunc("/subscriptions/{id}", h.UpdateSubscription).Methods("PUT")
|
||||||
|
r.HandleFunc("/subscriptions/{id}", h.DeleteSubscription).Methods("DELETE")
|
||||||
|
r.HandleFunc("/subscriptions", h.ListSubscriptions).Methods("GET")
|
||||||
|
r.HandleFunc("/subscriptions/sum", h.SumSubscriptions).Methods("GET")
|
||||||
|
r.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
|
||||||
|
logger.Info("Запущен сервер...", zap.String("port", cfg.ServerPort))
|
||||||
|
if err := http.ListenAndServe(":"+cfg.ServerPort, r); err != nil {
|
||||||
|
logger.Fatal("Ошибка запуска сервера", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
8
migrations/0001_create_subscriptions_table.up.sql
Normal file
8
migrations/0001_create_subscriptions_table.up.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS subscriptions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
service_name VARCHAR(255) NOT NULL,
|
||||||
|
price INTEGER NOT NULL,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
start_date DATE NOT NULL,
|
||||||
|
end_date DATE
|
||||||
|
);
|
||||||
16
vendor/github.com/KyleBanks/depth/.gitignore
generated
vendored
Normal file
16
vendor/github.com/KyleBanks/depth/.gitignore
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
|
||||||
|
bin/
|
||||||
9
vendor/github.com/KyleBanks/depth/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/KyleBanks/depth/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.9.x
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||||
|
#script: go test $(go list ./... | grep -v vendor/)
|
||||||
21
vendor/github.com/KyleBanks/depth/LICENSE
generated
vendored
Normal file
21
vendor/github.com/KyleBanks/depth/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Kyle Banks
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
32
vendor/github.com/KyleBanks/depth/Makefile
generated
vendored
Normal file
32
vendor/github.com/KyleBanks/depth/Makefile
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
VERSION = 1.2.1
|
||||||
|
|
||||||
|
RELEASE_PKG = ./cmd/depth
|
||||||
|
INSTALL_PKG = $(RELEASE_PKG)
|
||||||
|
|
||||||
|
|
||||||
|
# Remote includes require 'mmake'
|
||||||
|
# github.com/tj/mmake
|
||||||
|
include github.com/KyleBanks/make/go/install
|
||||||
|
include github.com/KyleBanks/make/go/sanity
|
||||||
|
include github.com/KyleBanks/make/go/release
|
||||||
|
include github.com/KyleBanks/make/go/bench
|
||||||
|
include github.com/KyleBanks/make/git/precommit
|
||||||
|
|
||||||
|
# Runs a number of depth commands as examples of what's possible.
|
||||||
|
example: | install
|
||||||
|
depth github.com/KyleBanks/depth/cmd/depth strings ./
|
||||||
|
|
||||||
|
depth -internal strings
|
||||||
|
|
||||||
|
depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test github.com/KyleBanks/depth/cmd/depth
|
||||||
|
|
||||||
|
depth -test -internal strings
|
||||||
|
|
||||||
|
depth -test -internal -max 3 strings
|
||||||
|
|
||||||
|
depth .
|
||||||
|
|
||||||
|
depth ./cmd/depth
|
||||||
|
.PHONY: example
|
||||||
232
vendor/github.com/KyleBanks/depth/README.md
generated
vendored
Normal file
232
vendor/github.com/KyleBanks/depth/README.md
generated
vendored
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# depth
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/KyleBanks/depth)
|
||||||
|
[](https://travis-ci.org/KyleBanks/depth)
|
||||||
|
[](https://goreportcard.com/report/github.com/KyleBanks/depth)
|
||||||
|
[](https://coveralls.io/github/KyleBanks/depth?branch=master)
|
||||||
|
|
||||||
|
`depth` is tool to retrieve and visualize Go source code dependency trees.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Download the appropriate binary for your platform from the [Releases](https://github.com/KyleBanks/depth/releases) page, or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/KyleBanks/depth/cmd/depth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`depth` can be used as a standalone command-line application, or as a package within your own project.
|
||||||
|
|
||||||
|
### Command-Line
|
||||||
|
|
||||||
|
Simply execute `depth` with one or more package names to visualize. You can use the fully qualified import path of the package, like so:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
12 dependencies (11 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can use a relative path, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth .
|
||||||
|
$ depth ./cmd/depth
|
||||||
|
$ depth ../
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `depth` on the Go standard library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
Visualizing multiple packages at a time is supported by simply naming the packages you'd like to visualize:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth strings github.com/KyleBanks/depth
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
5 dependencies (5 internal, 0 external, 0 testing).
|
||||||
|
github.com/KyleBanks/depth
|
||||||
|
├ fmt
|
||||||
|
├ go/build
|
||||||
|
├ path
|
||||||
|
├ sort
|
||||||
|
└ strings
|
||||||
|
7 dependencies (7 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-internal`
|
||||||
|
|
||||||
|
By default, `depth` only resolves the top level of dependencies for standard library packages, however you can use the `-internal` flag to visualize all internal dependencies:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -internal strings
|
||||||
|
strings
|
||||||
|
├ errors
|
||||||
|
├ io
|
||||||
|
├ errors
|
||||||
|
└ sync
|
||||||
|
├ internal/race
|
||||||
|
└ unsafe
|
||||||
|
├ runtime
|
||||||
|
├ runtime/internal/atomic
|
||||||
|
└ unsafe
|
||||||
|
├ runtime/internal/sys
|
||||||
|
└ unsafe
|
||||||
|
├ sync/atomic
|
||||||
|
└ unsafe
|
||||||
|
└ unsafe
|
||||||
|
├ unicode
|
||||||
|
└ unicode/utf8
|
||||||
|
12 dependencies (12 internal, 0 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-max`
|
||||||
|
|
||||||
|
The `-max` flag limits the dependency tree to the maximum depth provided. For example, if you supply `-max 1` on the `depth` package, your output would look like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ depth -max 1 github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth
|
||||||
|
├ encoding/json
|
||||||
|
├ flag
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ log
|
||||||
|
├ os
|
||||||
|
├ strings
|
||||||
|
└ github.com/KyleBanks/depth
|
||||||
|
7 dependencies (6 internal, 1 external, 0 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-max` flag is particularly useful in conjunction with the `-internal` flag which can lead to very deep dependency trees.
|
||||||
|
|
||||||
|
#### `-test`
|
||||||
|
|
||||||
|
By default, `depth` ignores dependencies that are only required for testing. However, you can view test dependencies using the `-test` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -test strings
|
||||||
|
strings
|
||||||
|
├ bytes
|
||||||
|
├ errors
|
||||||
|
├ fmt
|
||||||
|
├ io
|
||||||
|
├ io/ioutil
|
||||||
|
├ math/rand
|
||||||
|
├ reflect
|
||||||
|
├ sync
|
||||||
|
├ testing
|
||||||
|
├ unicode
|
||||||
|
├ unicode/utf8
|
||||||
|
└ unsafe
|
||||||
|
13 dependencies (13 internal, 0 external, 8 testing).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-explain target-package`
|
||||||
|
|
||||||
|
The `-explain` flag instructs `depth` to print import chains in which the
|
||||||
|
`target-package` is found:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -explain strings github.com/KyleBanks/depth/cmd/depth
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> strings
|
||||||
|
github.com/KyleBanks/depth/cmd/depth -> github.com/KyleBanks/depth -> strings
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `-json`
|
||||||
|
|
||||||
|
The `-json` flag instructs `depth` to output dependencies in JSON format:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ depth -json github.com/KyleBanks/depth/cmd/depth
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth/cmd/depth",
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "encoding/json",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
{
|
||||||
|
"name": "github.com/KyleBanks/depth",
|
||||||
|
"internal": false,
|
||||||
|
"deps": [
|
||||||
|
{
|
||||||
|
"name": "go/build",
|
||||||
|
"internal": true,
|
||||||
|
"deps": null
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integrating With Your Project
|
||||||
|
|
||||||
|
The `depth` package can easily be used to retrieve the dependency tree for a particular package in your own project. For example, here's how you would retrieve the dependency tree for the `strings` package:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
var t depth.Tree
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output: "'strings' has 4 dependencies."
|
||||||
|
log.Printf("'%v' has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
```
|
||||||
|
|
||||||
|
For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/KyleBanks/depth"
|
||||||
|
|
||||||
|
t := depth.Tree {
|
||||||
|
ResolveInternal: true,
|
||||||
|
ResolveTest: true,
|
||||||
|
MaxDepth: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err := t.Resolve("strings")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
`depth` was developed by [Kyle Banks](https://twitter.com/kylewbanks).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`depth` is available under the [MIT](./LICENSE) license.
|
||||||
129
vendor/github.com/KyleBanks/depth/depth.go
generated
vendored
Normal file
129
vendor/github.com/KyleBanks/depth/depth.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of
|
||||||
|
// internal and external packages.
|
||||||
|
//
|
||||||
|
// For example, the dependencies of the stdlib `strings` package can be resolved like so:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// var t depth.Tree
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Output: "strings has 4 dependencies."
|
||||||
|
// log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps))
|
||||||
|
//
|
||||||
|
// For additional customization, simply set the appropriate flags on the `Tree` before resolving:
|
||||||
|
//
|
||||||
|
// import "github.com/KyleBanks/depth"
|
||||||
|
//
|
||||||
|
// t := depth.Tree {
|
||||||
|
// ResolveInternal: true,
|
||||||
|
// ResolveTest: true,
|
||||||
|
// MaxDepth: 10,
|
||||||
|
// }
|
||||||
|
// err := t.Resolve("strings")
|
||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved,
|
||||||
|
// typically because it does not exist.
|
||||||
|
var ErrRootPkgNotResolved = errors.New("unable to resolve root package")
|
||||||
|
|
||||||
|
// Importer defines a type that can import a package and return its details.
|
||||||
|
type Importer interface {
|
||||||
|
Import(name, srcDir string, im build.ImportMode) (*build.Package, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree represents the top level of a Pkg and the configuration used to
|
||||||
|
// initialize and represent its contents.
|
||||||
|
type Tree struct {
|
||||||
|
Root *Pkg
|
||||||
|
|
||||||
|
ResolveInternal bool
|
||||||
|
ResolveTest bool
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
Importer Importer
|
||||||
|
|
||||||
|
importCache map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the root Pkg name provided,
|
||||||
|
// and the packages it depends on.
|
||||||
|
func (t *Tree) Resolve(name string) error {
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root = &Pkg{
|
||||||
|
Name: name,
|
||||||
|
Tree: t,
|
||||||
|
SrcDir: pwd,
|
||||||
|
Test: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the import cache each time to ensure a reused Tree doesn't
|
||||||
|
// reuse the same cache.
|
||||||
|
t.importCache = nil
|
||||||
|
|
||||||
|
// Allow custom importers, but use build.Default if none is provided.
|
||||||
|
if t.Importer == nil {
|
||||||
|
t.Importer = &build.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Root.Resolve(t.Importer)
|
||||||
|
if !t.Root.Resolved {
|
||||||
|
return ErrRootPkgNotResolved
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldResolveInternal determines if internal packages should be further resolved beyond the
|
||||||
|
// current parent.
|
||||||
|
//
|
||||||
|
// For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the
|
||||||
|
// internal dependencies it relies on will be resolved. If for example `strings` is one of those
|
||||||
|
// dependencies, and it is passed as the parent here, false may be returned and its internal
|
||||||
|
// dependencies will not be resolved.
|
||||||
|
func (t *Tree) shouldResolveInternal(parent *Pkg) bool {
|
||||||
|
if t.ResolveInternal {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent == t.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum
|
||||||
|
// depth allowed by the tree.
|
||||||
|
//
|
||||||
|
// If the Tree has a MaxDepth of zero, true is never returned.
|
||||||
|
func (t *Tree) isAtMaxDepth(p *Pkg) bool {
|
||||||
|
if t.MaxDepth == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.depth() >= t.MaxDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasSeenImport returns true if the import name provided has already been seen within the tree.
|
||||||
|
// This function only returns false for a name once.
|
||||||
|
func (t *Tree) hasSeenImport(name string) bool {
|
||||||
|
if t.importCache == nil {
|
||||||
|
t.importCache = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := t.importCache[name]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t.importCache[name] = struct{}{}
|
||||||
|
return false
|
||||||
|
}
|
||||||
184
vendor/github.com/KyleBanks/depth/pkg.go
generated
vendored
Normal file
184
vendor/github.com/KyleBanks/depth/pkg.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package depth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/build"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pkg represents a Go source package, and its dependencies.
|
||||||
|
type Pkg struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
SrcDir string `json:"-"`
|
||||||
|
|
||||||
|
Internal bool `json:"internal"`
|
||||||
|
Resolved bool `json:"resolved"`
|
||||||
|
Test bool `json:"-"`
|
||||||
|
|
||||||
|
Tree *Tree `json:"-"`
|
||||||
|
Parent *Pkg `json:"-"`
|
||||||
|
Deps []Pkg `json:"deps"`
|
||||||
|
|
||||||
|
Raw *build.Package `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve recursively finds all dependencies for the Pkg and the packages it depends on.
|
||||||
|
func (p *Pkg) Resolve(i Importer) {
|
||||||
|
// Resolved is always true, regardless of if we skip the import,
|
||||||
|
// it is only false if there is an error while importing.
|
||||||
|
p.Resolved = true
|
||||||
|
|
||||||
|
name := p.cleanName()
|
||||||
|
if name == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop resolving imports if we've reached max depth or found a duplicate.
|
||||||
|
var importMode build.ImportMode
|
||||||
|
if p.Tree.hasSeenImport(name) || p.Tree.isAtMaxDepth(p) {
|
||||||
|
importMode = build.FindOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg, err := i.Import(name, p.SrcDir, importMode)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check the error type?
|
||||||
|
p.Resolved = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Raw = pkg
|
||||||
|
|
||||||
|
// Update the name with the fully qualified import path.
|
||||||
|
p.Name = pkg.ImportPath
|
||||||
|
|
||||||
|
// If this is an internal dependency, we may need to skip it.
|
||||||
|
if pkg.Goroot {
|
||||||
|
p.Internal = true
|
||||||
|
if !p.Tree.shouldResolveInternal(p) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//first we set the regular dependencies, then we add the test dependencies
|
||||||
|
//sharing the same set. This allows us to mark all test-only deps linearly
|
||||||
|
unique := make(map[string]struct{})
|
||||||
|
p.setDeps(i, pkg.Imports, pkg.Dir, unique, false)
|
||||||
|
if p.Tree.ResolveTest {
|
||||||
|
p.setDeps(i, append(pkg.TestImports, pkg.XTestImports...), pkg.Dir, unique, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDeps takes a slice of import paths and the source directory they are relative to,
|
||||||
|
// and creates the Deps of the Pkg. Each dependency is also further resolved prior to being added
|
||||||
|
// to the Pkg.
|
||||||
|
func (p *Pkg) setDeps(i Importer, imports []string, srcDir string, unique map[string]struct{}, isTest bool) {
|
||||||
|
for _, imp := range imports {
|
||||||
|
// Mostly for testing files where cyclic imports are allowed.
|
||||||
|
if imp == p.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip duplicates.
|
||||||
|
if _, ok := unique[imp]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[imp] = struct{}{}
|
||||||
|
|
||||||
|
p.addDep(i, imp, srcDir, isTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byInternalAndName(p.Deps))
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDep creates a Pkg and it's dependencies from an imported package name.
|
||||||
|
func (p *Pkg) addDep(i Importer, name string, srcDir string, isTest bool) {
|
||||||
|
dep := Pkg{
|
||||||
|
Name: name,
|
||||||
|
SrcDir: srcDir,
|
||||||
|
Tree: p.Tree,
|
||||||
|
Parent: p,
|
||||||
|
Test: isTest,
|
||||||
|
}
|
||||||
|
dep.Resolve(i)
|
||||||
|
|
||||||
|
p.Deps = append(p.Deps, dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isParent goes recursively up the chain of Pkgs to determine if the name provided is ever a
|
||||||
|
// parent of the current Pkg.
|
||||||
|
func (p *Pkg) isParent(name string) bool {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Parent.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.isParent(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// depth returns the depth of the Pkg within the Tree.
|
||||||
|
func (p *Pkg) depth() int {
|
||||||
|
if p.Parent == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Parent.depth() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanName returns a cleaned version of the Pkg name used for resolving dependencies.
|
||||||
|
//
|
||||||
|
// If an empty string is returned, dependencies should not be resolved.
|
||||||
|
func (p *Pkg) cleanName() string {
|
||||||
|
name := p.Name
|
||||||
|
|
||||||
|
// C 'package' cannot be resolved.
|
||||||
|
if name == "C" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal golang_org/* packages must be prefixed with vendor/
|
||||||
|
//
|
||||||
|
// Thanks to @davecheney for this:
|
||||||
|
// https://github.com/davecheney/graphpkg/blob/master/main.go#L46
|
||||||
|
if strings.HasPrefix(name, "golang_org") {
|
||||||
|
name = path.Join("vendor", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Pkg containing the Pkg name and status.
|
||||||
|
func (p *Pkg) String() string {
|
||||||
|
b := bytes.NewBufferString(p.Name)
|
||||||
|
|
||||||
|
if !p.Resolved {
|
||||||
|
b.Write([]byte(" (unresolved)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// byInternalAndName ensures a slice of Pkgs are sorted such that the internal stdlib
|
||||||
|
// packages are always above external packages (ie. github.com/whatever).
|
||||||
|
type byInternalAndName []Pkg
|
||||||
|
|
||||||
|
func (b byInternalAndName) Len() int {
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Swap(i, j int) {
|
||||||
|
b[i], b[j] = b[j], b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byInternalAndName) Less(i, j int) bool {
|
||||||
|
if b[i].Internal && !b[j].Internal {
|
||||||
|
return true
|
||||||
|
} else if !b[i].Internal && b[j].Internal {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return b[i].Name < b[j].Name
|
||||||
|
}
|
||||||
14
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
Normal file
14
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
freebsd_task:
|
||||||
|
name: 'FreeBSD'
|
||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-14-2
|
||||||
|
install_script:
|
||||||
|
- pkg update -f
|
||||||
|
- pkg install -y go
|
||||||
|
test_script:
|
||||||
|
# run tests as user "cirrus" instead of root
|
||||||
|
- pw useradd cirrus -m
|
||||||
|
- chown -R cirrus:cirrus .
|
||||||
|
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
||||||
|
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
||||||
|
- FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...
|
||||||
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
10
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# go test -c output
|
||||||
|
*.test
|
||||||
|
*.test.exe
|
||||||
|
|
||||||
|
# Output of go build ./cmd/fsnotify
|
||||||
|
/fsnotify
|
||||||
|
/fsnotify.exe
|
||||||
|
|
||||||
|
/test/kqueue
|
||||||
|
/test/a.out
|
||||||
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||||
|
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
||||||
602
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
602
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
1.9.0 2024-04-04
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- all: make BufferedWatcher buffered again ([#657])
|
||||||
|
|
||||||
|
- inotify: fix race when adding/removing watches while a watched path is being
|
||||||
|
deleted ([#678], [#686])
|
||||||
|
|
||||||
|
- inotify: don't send empty event if a watched path is unmounted ([#655])
|
||||||
|
|
||||||
|
- inotify: don't register duplicate watches when watching both a symlink and its
|
||||||
|
target; previously that would get "half-added" and removing the second would
|
||||||
|
panic ([#679])
|
||||||
|
|
||||||
|
- kqueue: fix watching relative symlinks ([#681])
|
||||||
|
|
||||||
|
- kqueue: correctly mark pre-existing entries when watching a link to a dir on
|
||||||
|
kqueue ([#682])
|
||||||
|
|
||||||
|
- illumos: don't send error if changed file is deleted while processing the
|
||||||
|
event ([#678])
|
||||||
|
|
||||||
|
|
||||||
|
[#657]: https://github.com/fsnotify/fsnotify/pull/657
|
||||||
|
[#678]: https://github.com/fsnotify/fsnotify/pull/678
|
||||||
|
[#686]: https://github.com/fsnotify/fsnotify/pull/686
|
||||||
|
[#655]: https://github.com/fsnotify/fsnotify/pull/655
|
||||||
|
[#681]: https://github.com/fsnotify/fsnotify/pull/681
|
||||||
|
[#679]: https://github.com/fsnotify/fsnotify/pull/679
|
||||||
|
[#682]: https://github.com/fsnotify/fsnotify/pull/682
|
||||||
|
|
||||||
|
1.8.0 2024-10-31
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `FSNOTIFY_DEBUG` to print debug logs to stderr ([#619])
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- windows: fix behaviour of `WatchList()` to be consistent with other platforms ([#610])
|
||||||
|
|
||||||
|
- kqueue: ignore events with Ident=0 ([#590])
|
||||||
|
|
||||||
|
- kqueue: set O_CLOEXEC to prevent passing file descriptors to children ([#617])
|
||||||
|
|
||||||
|
- kqueue: emit events as "/path/dir/file" instead of "path/link/file" when watching a symlink ([#625])
|
||||||
|
|
||||||
|
- inotify: don't send event for IN_DELETE_SELF when also watching the parent ([#620])
|
||||||
|
|
||||||
|
- inotify: fix panic when calling Remove() in a goroutine ([#650])
|
||||||
|
|
||||||
|
- fen: allow watching subdirectories of watched directories ([#621])
|
||||||
|
|
||||||
|
[#590]: https://github.com/fsnotify/fsnotify/pull/590
|
||||||
|
[#610]: https://github.com/fsnotify/fsnotify/pull/610
|
||||||
|
[#617]: https://github.com/fsnotify/fsnotify/pull/617
|
||||||
|
[#619]: https://github.com/fsnotify/fsnotify/pull/619
|
||||||
|
[#620]: https://github.com/fsnotify/fsnotify/pull/620
|
||||||
|
[#621]: https://github.com/fsnotify/fsnotify/pull/621
|
||||||
|
[#625]: https://github.com/fsnotify/fsnotify/pull/625
|
||||||
|
[#650]: https://github.com/fsnotify/fsnotify/pull/650
|
||||||
|
|
||||||
|
1.7.0 - 2023-10-22
|
||||||
|
------------------
|
||||||
|
This version of fsnotify needs Go 1.17.
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- illumos: add FEN backend to support illumos and Solaris. ([#371])
|
||||||
|
|
||||||
|
- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful
|
||||||
|
in cases where you can't control the kernel buffer and receive a large number
|
||||||
|
of events in bursts. ([#550], [#572])
|
||||||
|
|
||||||
|
- all: add `AddWith()`, which is identical to `Add()` but allows passing
|
||||||
|
options. ([#521])
|
||||||
|
|
||||||
|
- windows: allow setting the ReadDirectoryChangesW() buffer size with
|
||||||
|
`fsnotify.WithBufferSize()`; the default of 64K is the highest value that
|
||||||
|
works on all platforms and is enough for most purposes, but in some cases a
|
||||||
|
highest buffer is needed. ([#521])
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- inotify: remove watcher if a watched path is renamed ([#518])
|
||||||
|
|
||||||
|
After a rename the reported name wasn't updated, or even an empty string.
|
||||||
|
Inotify doesn't provide any good facilities to update it, so just remove the
|
||||||
|
watcher. This is already how it worked on kqueue and FEN.
|
||||||
|
|
||||||
|
On Windows this does work, and remains working.
|
||||||
|
|
||||||
|
- windows: don't listen for file attribute changes ([#520])
|
||||||
|
|
||||||
|
File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API,
|
||||||
|
with no way to see if they're a file write or attribute change, so would show
|
||||||
|
up as a fsnotify.Write event. This is never useful, and could result in many
|
||||||
|
spurious Write events.
|
||||||
|
|
||||||
|
- windows: return `ErrEventOverflow` if the buffer is full ([#525])
|
||||||
|
|
||||||
|
Before it would merely return "short read", making it hard to detect this
|
||||||
|
error.
|
||||||
|
|
||||||
|
- kqueue: make sure events for all files are delivered properly when removing a
|
||||||
|
watched directory ([#526])
|
||||||
|
|
||||||
|
Previously they would get sent with `""` (empty string) or `"."` as the path
|
||||||
|
name.
|
||||||
|
|
||||||
|
- kqueue: don't emit spurious Create events for symbolic links ([#524])
|
||||||
|
|
||||||
|
The link would get resolved but kqueue would "forget" it already saw the link
|
||||||
|
itself, resulting on a Create for every Write event for the directory.
|
||||||
|
|
||||||
|
- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516])
|
||||||
|
|
||||||
|
- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in
|
||||||
|
`backend_other.go`, making it easier to use on unsupported platforms such as
|
||||||
|
WASM, AIX, etc. ([#528])
|
||||||
|
|
||||||
|
- other: use the `backend_other.go` no-op if the `appengine` build tag is set;
|
||||||
|
Google AppEngine forbids usage of the unsafe package so the inotify backend
|
||||||
|
won't compile there.
|
||||||
|
|
||||||
|
[#371]: https://github.com/fsnotify/fsnotify/pull/371
|
||||||
|
[#516]: https://github.com/fsnotify/fsnotify/pull/516
|
||||||
|
[#518]: https://github.com/fsnotify/fsnotify/pull/518
|
||||||
|
[#520]: https://github.com/fsnotify/fsnotify/pull/520
|
||||||
|
[#521]: https://github.com/fsnotify/fsnotify/pull/521
|
||||||
|
[#524]: https://github.com/fsnotify/fsnotify/pull/524
|
||||||
|
[#525]: https://github.com/fsnotify/fsnotify/pull/525
|
||||||
|
[#526]: https://github.com/fsnotify/fsnotify/pull/526
|
||||||
|
[#528]: https://github.com/fsnotify/fsnotify/pull/528
|
||||||
|
[#537]: https://github.com/fsnotify/fsnotify/pull/537
|
||||||
|
[#550]: https://github.com/fsnotify/fsnotify/pull/550
|
||||||
|
[#572]: https://github.com/fsnotify/fsnotify/pull/572
|
||||||
|
|
||||||
|
1.6.0 - 2022-10-13
|
||||||
|
------------------
|
||||||
|
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
||||||
|
but not documented). It also increases the minimum Linux version to 2.6.32.
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
||||||
|
|
||||||
|
This makes checking events a lot easier; for example:
|
||||||
|
|
||||||
|
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Becomes:
|
||||||
|
|
||||||
|
if event.Has(Write) && !event.Has(Remove) {
|
||||||
|
}
|
||||||
|
|
||||||
|
- all: add cmd/fsnotify ([#463])
|
||||||
|
|
||||||
|
A command-line utility for testing and some examples.
|
||||||
|
|
||||||
|
### Changes and fixes
|
||||||
|
|
||||||
|
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
||||||
|
|
||||||
|
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
||||||
|
still exists before emitting events.
|
||||||
|
|
||||||
|
This was inconsistent with other platforms and resulted in inconsistent event
|
||||||
|
reporting (e.g. when a file is quickly removed and re-created), and generally
|
||||||
|
a source of confusion. It was added in 2013 to fix a memory leak that no
|
||||||
|
longer exists.
|
||||||
|
|
||||||
|
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
||||||
|
not watched ([#460])
|
||||||
|
|
||||||
|
- inotify: replace epoll() with non-blocking inotify ([#434])
|
||||||
|
|
||||||
|
Non-blocking inotify was not generally available at the time this library was
|
||||||
|
written in 2014, but now it is. As a result, the minimum Linux version is
|
||||||
|
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
||||||
|
|
||||||
|
- kqueue: don't check for events every 100ms ([#480])
|
||||||
|
|
||||||
|
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
||||||
|
it waits until there is something to do.
|
||||||
|
|
||||||
|
- macos: retry opening files on EINTR ([#475])
|
||||||
|
|
||||||
|
- kqueue: skip unreadable files ([#479])
|
||||||
|
|
||||||
|
kqueue requires a file descriptor for every file in a directory; this would
|
||||||
|
fail if a file was unreadable by the current user. Now these files are simply
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
||||||
|
|
||||||
|
- windows: increase buffer size from 4K to 64K ([#485])
|
||||||
|
|
||||||
|
- windows: close file handle on Remove() ([#288])
|
||||||
|
|
||||||
|
- kqueue: put pathname in the error if watching a file fails ([#471])
|
||||||
|
|
||||||
|
- inotify, windows: calling Close() more than once could race ([#465])
|
||||||
|
|
||||||
|
- kqueue: improve Close() performance ([#233])
|
||||||
|
|
||||||
|
- all: various documentation additions and clarifications.
|
||||||
|
|
||||||
|
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
||||||
|
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
||||||
|
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
||||||
|
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
||||||
|
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
||||||
|
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
||||||
|
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
||||||
|
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
||||||
|
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
||||||
|
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
||||||
|
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
||||||
|
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
||||||
|
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
||||||
|
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
||||||
|
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-04-25
|
||||||
|
|
||||||
|
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||||
|
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||||
|
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-04-22
|
||||||
|
|
||||||
|
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-04-21
|
||||||
|
|
||||||
|
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||||
|
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||||
|
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||||
|
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||||
|
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-08-24
|
||||||
|
|
||||||
|
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-08-20
|
||||||
|
|
||||||
|
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||||
|
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||||
|
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||||
|
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||||
|
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||||
|
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||||
|
|
||||||
|
## [1.4.9] - 2020-03-11
|
||||||
|
|
||||||
|
* Move example usage to the readme #329. This may resolve #328.
|
||||||
|
|
||||||
|
## [1.4.8] - 2020-03-10
|
||||||
|
|
||||||
|
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
||||||
|
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
||||||
|
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
||||||
|
* CI: Less verbosity (@nathany #267)
|
||||||
|
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
||||||
|
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
||||||
|
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
||||||
|
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
||||||
|
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
||||||
|
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
||||||
|
* Linux: open files with close-on-exec (@linxiulei #273)
|
||||||
|
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
||||||
|
* Project: Add go.mod (@nathany #309)
|
||||||
|
* Project: Revise editor config (@nathany #309)
|
||||||
|
* Project: Update copyright for 2019 (@nathany #309)
|
||||||
|
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
||||||
|
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
||||||
|
|
||||||
|
## [1.4.7] - 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## [1.4.2] - 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.1] - 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.0] - 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## [1.3.1] - 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## [1.3.0] - 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## [1.2.10] - 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## [1.2.9] - 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## [1.2.8] - 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## [1.2.5] - 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## [1.2.1] - 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## [1.2.0] - 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## [1.1.1] - 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## [1.1.0] - 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [1.0.4] - 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## [1.0.3] - 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## [1.0.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## [1.0.0] - 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## [0.9.3] - 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [0.9.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## [0.9.1] - 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## [0.9.0] - 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## [0.8.12] - 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## [0.8.11] - 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## [0.8.10] - 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## [0.8.9] - 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## [0.8.8] - 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## [0.8.7] - 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## [0.8.6] - 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## [0.8.5] - 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## [0.8.4] - 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## [0.8.3] - 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## [0.8.2] - 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## [0.8.1] - 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## [0.8.0] - 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## [0.7.4] - 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## [0.7.3] - 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## [0.7.2] - 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## [0.7.1] - 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## [0.7.0] - 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## [0.6.0] - 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## [0.5.1] - 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## [0.5.0] - 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## [0.4.0] - 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## [0.3.0] - 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## [0.2.0] - 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## [0.1.0] - 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
||||||
145
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
145
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
Thank you for your interest in contributing to fsnotify! We try to review and
|
||||||
|
merge PRs in a reasonable timeframe, but please be aware that:
|
||||||
|
|
||||||
|
- To avoid "wasted" work, please discuss changes on the issue tracker first. You
|
||||||
|
can just send PRs, but they may end up being rejected for one reason or the
|
||||||
|
other.
|
||||||
|
|
||||||
|
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
||||||
|
all supported platforms.
|
||||||
|
|
||||||
|
- Changes will need to be compatible; old code should still compile, and the
|
||||||
|
runtime behaviour can't change in ways that are likely to lead to problems for
|
||||||
|
users.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
||||||
|
platforms. Testing different platforms locally can be done with something like
|
||||||
|
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
||||||
|
|
||||||
|
Use the `-short` flag to make the "stress test" run faster.
|
||||||
|
|
||||||
|
Writing new tests
|
||||||
|
-----------------
|
||||||
|
Scripts in the testdata directory allow creating test cases in a "shell-like"
|
||||||
|
syntax. The basic format is:
|
||||||
|
|
||||||
|
script
|
||||||
|
|
||||||
|
Output:
|
||||||
|
desired output
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
# Create a new empty file with some data.
|
||||||
|
watch /
|
||||||
|
echo data >/file
|
||||||
|
|
||||||
|
Output:
|
||||||
|
create /file
|
||||||
|
write /file
|
||||||
|
|
||||||
|
Just create a new file to add a new test; select which tests to run with
|
||||||
|
`-run TestScript/[path]`.
|
||||||
|
|
||||||
|
script
|
||||||
|
------
|
||||||
|
The script is a "shell-like" script:
|
||||||
|
|
||||||
|
cmd arg arg
|
||||||
|
|
||||||
|
Comments are supported with `#`:
|
||||||
|
|
||||||
|
# Comment
|
||||||
|
cmd arg arg # Comment
|
||||||
|
|
||||||
|
All operations are done in a temp directory; a path like "/foo" is rewritten to
|
||||||
|
"/tmp/TestFoo/foo".
|
||||||
|
|
||||||
|
Arguments can be quoted with `"` or `'`; there are no escapes and they're
|
||||||
|
functionally identical right now, but this may change in the future, so best to
|
||||||
|
assume shell-like rules.
|
||||||
|
|
||||||
|
touch "/file with spaces"
|
||||||
|
|
||||||
|
End-of-line escapes with `\` are not supported.
|
||||||
|
|
||||||
|
### Supported commands
|
||||||
|
|
||||||
|
watch path [ops] # Watch the path, reporting events for it. Nothing is
|
||||||
|
# watched by default. Optionally a list of ops can be
|
||||||
|
# given, as with AddWith(path, WithOps(...)).
|
||||||
|
unwatch path # Stop watching the path.
|
||||||
|
watchlist n # Assert watchlist length.
|
||||||
|
|
||||||
|
stop # Stop running the script; for debugging.
|
||||||
|
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
|
||||||
|
parallel by default, so -parallel=1 is probably a good
|
||||||
|
idea).
|
||||||
|
print [any strings] # Print text to stdout; for debugging.
|
||||||
|
|
||||||
|
touch path
|
||||||
|
mkdir [-p] dir
|
||||||
|
ln -s target link # Only ln -s supported.
|
||||||
|
mkfifo path
|
||||||
|
mknod dev path
|
||||||
|
mv src dst
|
||||||
|
rm [-r] path
|
||||||
|
chmod mode path # Octal only
|
||||||
|
sleep time-in-ms
|
||||||
|
|
||||||
|
cat path # Read path (does nothing with the data; just reads it).
|
||||||
|
echo str >>path # Append "str" to "path".
|
||||||
|
echo str >path # Truncate "path" and write "str".
|
||||||
|
|
||||||
|
require reason # Skip the test if "reason" is true; "skip" and
|
||||||
|
skip reason # "require" behave identical; it supports both for
|
||||||
|
# readability. Possible reasons are:
|
||||||
|
#
|
||||||
|
# always Always skip this test.
|
||||||
|
# symlink Symlinks are supported (requires admin
|
||||||
|
# permissions on Windows).
|
||||||
|
# mkfifo Platform doesn't support FIFO named sockets.
|
||||||
|
# mknod Platform doesn't support device nodes.
|
||||||
|
|
||||||
|
|
||||||
|
output
|
||||||
|
------
|
||||||
|
After `Output:` the desired output is given; this is indented by convention, but
|
||||||
|
that's not required.
|
||||||
|
|
||||||
|
The format of that is:
|
||||||
|
|
||||||
|
# Comment
|
||||||
|
event path # Comment
|
||||||
|
|
||||||
|
system:
|
||||||
|
event path
|
||||||
|
system2:
|
||||||
|
event path
|
||||||
|
|
||||||
|
Every event is one line, and any whitespace between the event and path are
|
||||||
|
ignored. The path can optionally be surrounded in ". Anything after a "#" is
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
Platform-specific tests can be added after GOOS; for example:
|
||||||
|
|
||||||
|
watch /
|
||||||
|
touch /file
|
||||||
|
|
||||||
|
Output:
|
||||||
|
# Tested if nothing else matches
|
||||||
|
create /file
|
||||||
|
|
||||||
|
# Windows-specific test.
|
||||||
|
windows:
|
||||||
|
write /file
|
||||||
|
|
||||||
|
You can specify multiple platforms with a comma (e.g. "windows, linux:").
|
||||||
|
"kqueue" is a shortcut for all kqueue systems (BSD, macOS).
|
||||||
|
|
||||||
|
|
||||||
|
[goon]: https://github.com/arp242/goon
|
||||||
|
[Vagrant]: https://www.vagrantup.com/
|
||||||
|
[integration_test.go]: /integration_test.go
|
||||||
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright © 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright © fsnotify Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
182
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
182
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||||
|
Windows, Linux, macOS, BSD, and illumos.
|
||||||
|
|
||||||
|
Go 1.17 or newer is required; the full documentation is at
|
||||||
|
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Platform support:
|
||||||
|
|
||||||
|
| Backend | OS | Status |
|
||||||
|
| :-------------------- | :--------- | :------------------------------------------------------------------------ |
|
||||||
|
| inotify | Linux | Supported |
|
||||||
|
| kqueue | BSD, macOS | Supported |
|
||||||
|
| ReadDirectoryChangesW | Windows | Supported |
|
||||||
|
| FEN | illumos | Supported |
|
||||||
|
| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||||
|
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
||||||
|
| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
|
||||||
|
| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||||
|
|
||||||
|
Linux and illumos should include Android and Solaris, but these are currently
|
||||||
|
untested.
|
||||||
|
|
||||||
|
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
||||||
|
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
A basic example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create new watcher.
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
// Start listening for events.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Has(fsnotify.Write) {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Add a path.
|
||||||
|
err = watcher.Add("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block main goroutine forever.
|
||||||
|
<-make(chan struct{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
||||||
|
run with:
|
||||||
|
|
||||||
|
% go run ./cmd/fsnotify
|
||||||
|
|
||||||
|
Further detailed documentation can be found in godoc:
|
||||||
|
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
---
|
||||||
|
### Will a file still be watched when it's moved to another directory?
|
||||||
|
No, not unless you are watching the location it was moved to.
|
||||||
|
|
||||||
|
### Are subdirectories watched?
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive
|
||||||
|
watcher is on the roadmap: [#18]).
|
||||||
|
|
||||||
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
|
||||||
|
### Do I have to watch the Error and Event channels in a goroutine?
|
||||||
|
Yes. You can read both channels in the same goroutine using `select` (you don't
|
||||||
|
need a separate goroutine for both channels; see the example).
|
||||||
|
|
||||||
|
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
||||||
|
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
||||||
|
protocols does not provide network level support for file notifications, and
|
||||||
|
neither do the /proc and /sys virtual filesystems.
|
||||||
|
|
||||||
|
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
||||||
|
|
||||||
|
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
||||||
|
|
||||||
|
### Why do I get many Chmod events?
|
||||||
|
Some programs may generate a lot of attribute changes; for example Spotlight on
|
||||||
|
macOS, anti-virus programs, backup applications, and some others are known to do
|
||||||
|
this. As a rule, it's typically best to ignore Chmod events. They're often not
|
||||||
|
useful, and tend to cause problems.
|
||||||
|
|
||||||
|
Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
||||||
|
temporary workaround is to add your folder(s) to the *Spotlight Privacy
|
||||||
|
settings* until we have a native FSEvents implementation (see [#11]).
|
||||||
|
|
||||||
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
||||||
|
|
||||||
|
### Watching a file doesn't work well
|
||||||
|
Watching individual files (rather than directories) is generally not recommended
|
||||||
|
as many programs (especially editors) update files atomically: it will write to
|
||||||
|
a temporary file which is then moved to to destination, overwriting the original
|
||||||
|
(or some variant thereof). The watcher on the original file is now lost, as that
|
||||||
|
no longer exists.
|
||||||
|
|
||||||
|
The upshot of this is that a power failure or crash won't leave a half-written
|
||||||
|
file.
|
||||||
|
|
||||||
|
Watch the parent directory and use `Event.Name` to filter out files you're not
|
||||||
|
interested in. There is an example of this in `cmd/fsnotify/file.go`.
|
||||||
|
|
||||||
|
Platform-specific notes
|
||||||
|
-----------------------
|
||||||
|
### Linux
|
||||||
|
When a file is removed a REMOVE event won't be emitted until all file
|
||||||
|
descriptors are closed; it will emit a CHMOD instead:
|
||||||
|
|
||||||
|
fp := os.Open("file")
|
||||||
|
os.Remove("file") // CHMOD
|
||||||
|
fp.Close() // REMOVE
|
||||||
|
|
||||||
|
This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
|
||||||
|
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||||
|
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||||
|
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||||
|
"instance", and every path you add is a "watch".
|
||||||
|
|
||||||
|
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||||
|
`/proc/sys/fs/inotify/max_user_instances`
|
||||||
|
|
||||||
|
To increase them you can use `sysctl` or write the value to proc file:
|
||||||
|
|
||||||
|
# The default values on Linux 5.18
|
||||||
|
sysctl fs.inotify.max_user_watches=124983
|
||||||
|
sysctl fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||||
|
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||||
|
distro's documentation):
|
||||||
|
|
||||||
|
fs.inotify.max_user_watches=124983
|
||||||
|
fs.inotify.max_user_instances=128
|
||||||
|
|
||||||
|
Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
files" error.
|
||||||
|
|
||||||
|
### kqueue (macOS, all BSD systems)
|
||||||
|
kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
so if you're watching a directory with five files then that's six file
|
||||||
|
descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
these platforms.
|
||||||
|
|
||||||
|
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
||||||
|
control the maximum number of open files.
|
||||||
467
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
467
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
Normal file
@ -0,0 +1,467 @@
|
|||||||
|
//go:build solaris
|
||||||
|
|
||||||
|
// FEN backend for illumos (supported) and Solaris (untested, but should work).
|
||||||
|
//
|
||||||
|
// See port_create(3c) etc. for docs. https://www.illumos.org/man/3C/port_create
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fen struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
port *unix.EventPort
|
||||||
|
dirs map[string]Op // Explicitly watched directories
|
||||||
|
watches map[string]Op // Explicitly watched non-directories
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
w := &fen{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
dirs: make(map[string]Op),
|
||||||
|
watches: make(map[string]Op),
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
w.port, err = unix.NewEventPort()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.port.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *fen) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently we resolve symlinks that were explicitly requested to be
|
||||||
|
// watched. Otherwise we would use LStat here.
|
||||||
|
stat, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate all files in the directory.
|
||||||
|
if stat.IsDir() {
|
||||||
|
err := w.handleDirectory(name, stat, true, w.associateFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.dirs[name] = with.op
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.associateFile(name, stat, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = with.op
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !w.port.PathIsWatched(name) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user has expressed an intent. Immediately remove this name from
|
||||||
|
// whichever watch list it might be in. If it's not in there the delete
|
||||||
|
// doesn't cause harm.
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.dirs, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
stat, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove associations for every file in the directory.
|
||||||
|
if stat.IsDir() {
|
||||||
|
err := w.handleDirectory(name, stat, false, w.dissociateFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.port.DissociatePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents contains the main loop that runs in a goroutine watching for events.
|
||||||
|
func (w *fen) readEvents() {
|
||||||
|
// If this function returns, the watcher has been closed and we can close
|
||||||
|
// these channels
|
||||||
|
defer func() {
|
||||||
|
close(w.Errors)
|
||||||
|
close(w.Events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
pevents := make([]unix.PortEvent, 8)
|
||||||
|
for {
|
||||||
|
count, err := w.port.Get(pevents, 1, nil)
|
||||||
|
if err != nil && err != unix.ETIME {
|
||||||
|
// Interrupted system call (count should be 0) ignore and continue
|
||||||
|
if errors.Is(err, unix.EINTR) && count == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Get failed because we called w.Close()
|
||||||
|
if errors.Is(err, unix.EBADF) && w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// There was an error not caused by calling w.Close()
|
||||||
|
if !w.sendError(fmt.Errorf("port.Get: %w", err)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := pevents[:count]
|
||||||
|
for _, pevent := range p {
|
||||||
|
if pevent.Source != unix.PORT_SOURCE_FILE {
|
||||||
|
// Event from unexpected source received; should never happen.
|
||||||
|
if !w.sendError(errors.New("Event from unexpected source received")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(pevent.Path, pevent.Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.handleEvent(&pevent)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
|
||||||
|
files, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all children of the directory.
|
||||||
|
for _, entry := range files {
|
||||||
|
finfo, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = handler(filepath.Join(path, finfo.Name()), finfo, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally handle the directory itself.
|
||||||
|
return handler(path, stat, follow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvent might need to emit more than one fsnotify event if the events
|
||||||
|
// bitmap matches more than one event type (e.g. the file was both modified and
|
||||||
|
// had the attributes changed between when the association was created and the
|
||||||
|
// when event was returned)
|
||||||
|
func (w *fen) handleEvent(event *unix.PortEvent) error {
|
||||||
|
var (
|
||||||
|
events = event.Events
|
||||||
|
path = event.Path
|
||||||
|
fmode = event.Cookie.(os.FileMode)
|
||||||
|
reRegister = true
|
||||||
|
)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, watchedDir := w.dirs[path]
|
||||||
|
_, watchedPath := w.watches[path]
|
||||||
|
w.mu.Unlock()
|
||||||
|
isWatched := watchedDir || watchedPath
|
||||||
|
|
||||||
|
if events&unix.FILE_DELETE != 0 {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
reRegister = false
|
||||||
|
}
|
||||||
|
if events&unix.FILE_RENAME_FROM != 0 {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Rename}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't keep watching the new file name
|
||||||
|
reRegister = false
|
||||||
|
}
|
||||||
|
if events&unix.FILE_RENAME_TO != 0 {
|
||||||
|
// We don't report a Rename event for this case, because Rename events
|
||||||
|
// are interpreted as referring to the _old_ name of the file, and in
|
||||||
|
// this case the event would refer to the new name of the file. This
|
||||||
|
// type of rename event is not supported by fsnotify.
|
||||||
|
|
||||||
|
// inotify reports a Remove event in this case, so we simulate this
|
||||||
|
// here.
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't keep watching the file that was removed
|
||||||
|
reRegister = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The file is gone, nothing left to do.
|
||||||
|
if !reRegister {
|
||||||
|
if watchedDir {
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.dirs, path)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
if watchedPath {
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches, path)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't get a deletion the file still exists and we're going to have
|
||||||
|
// to watch it again. Let's Stat it now so that we can compare permissions
|
||||||
|
// and have what we need to continue watching the file
|
||||||
|
|
||||||
|
stat, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
// This is unexpected, but we should still emit an event. This happens
|
||||||
|
// most often on "rm -r" of a subdirectory inside a watched directory We
|
||||||
|
// get a modify event of something happening inside, but by the time we
|
||||||
|
// get here, the sudirectory is already gone. Clearly we were watching
|
||||||
|
// this path but now it is gone. Let's tell the user that it was
|
||||||
|
// removed.
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Suppress extra write events on removed directories; they are not
|
||||||
|
// informative and can be confusing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve symlinks that were explicitly watched as we would have at Add()
|
||||||
|
// time. this helps suppress spurious Chmod events on watched symlinks
|
||||||
|
if isWatched {
|
||||||
|
stat, err = os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// The symlink still exists, but the target is gone. Report the
|
||||||
|
// Remove similar to above.
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Remove}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't return the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if events&unix.FILE_MODIFIED != 0 {
|
||||||
|
if fmode.IsDir() && watchedDir {
|
||||||
|
if err := w.updateDirectory(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Write}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if events&unix.FILE_ATTRIB != 0 && stat != nil {
|
||||||
|
// Only send Chmod if perms changed
|
||||||
|
if stat.Mode().Perm() != fmode.Perm() {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Chmod}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat != nil {
|
||||||
|
// If we get here, it means we've hit an event above that requires us to
|
||||||
|
// continue watching the file or directory
|
||||||
|
err := w.associateFile(path, stat, isWatched)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// Path may have been removed since the stat.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The directory was modified, so we must find unwatched entities and watch
|
||||||
|
// them. If something was removed from the directory, nothing will happen, as
|
||||||
|
// everything else should still be watched.
|
||||||
|
func (w *fen) updateDirectory(path string) error {
|
||||||
|
files, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
// Directory no longer exists: probably just deleted since we got the
|
||||||
|
// event.
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range files {
|
||||||
|
path := filepath.Join(path, entry.Name())
|
||||||
|
if w.port.PathIsWatched(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
finfo, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.associateFile(path, finfo, false)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// File may have disappeared between getting the dir listing and
|
||||||
|
// adding the port: that's okay to ignore.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) associateFile(path string, stat os.FileInfo, follow bool) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
// This is primarily protecting the call to AssociatePath but it is
|
||||||
|
// important and intentional that the call to PathIsWatched is also
|
||||||
|
// protected by this mutex. Without this mutex, AssociatePath has been seen
|
||||||
|
// to error out that the path is already associated.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
if w.port.PathIsWatched(path) {
|
||||||
|
// Remove the old association in favor of this one If we get ENOENT,
|
||||||
|
// then while the x/sys/unix wrapper still thought that this path was
|
||||||
|
// associated, the underlying event port did not. This call will have
|
||||||
|
// cleared up that discrepancy. The most likely cause is that the event
|
||||||
|
// has fired but we haven't processed it yet.
|
||||||
|
err := w.port.DissociatePath(path)
|
||||||
|
if err != nil && !errors.Is(err, unix.ENOENT) {
|
||||||
|
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var events int
|
||||||
|
if !follow {
|
||||||
|
// Watch symlinks themselves rather than their targets unless this entry
|
||||||
|
// is explicitly watched.
|
||||||
|
events |= unix.FILE_NOFOLLOW
|
||||||
|
}
|
||||||
|
if true { // TODO: implement withOps()
|
||||||
|
events |= unix.FILE_MODIFIED
|
||||||
|
}
|
||||||
|
if true {
|
||||||
|
events |= unix.FILE_ATTRIB
|
||||||
|
}
|
||||||
|
err := w.port.AssociatePath(path, stat, events, stat.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port.AssociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) dissociateFile(path string, stat os.FileInfo, unused bool) error {
|
||||||
|
if !w.port.PathIsWatched(path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := w.port.DissociatePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port.DissociatePath(%q): %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches)+len(w.dirs))
|
||||||
|
for pathname := range w.dirs {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
for pathname := range w.watches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fen) xSupports(op Op) bool {
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
583
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
583
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
Normal file
@ -0,0 +1,583 @@
|
|||||||
|
//go:build linux && !appengine
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inotify struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
// Store fd here as os.File.Read() will no longer return on close after
|
||||||
|
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
||||||
|
fd int
|
||||||
|
inotifyFile *os.File
|
||||||
|
watches *watches
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
|
||||||
|
// Store rename cookies in an array, with the index wrapping to 0. Almost
|
||||||
|
// all of the time what we get is a MOVED_FROM to set the cookie and the
|
||||||
|
// next event inotify sends will be MOVED_TO to read it. However, this is
|
||||||
|
// not guaranteed – as described in inotify(7) – and we may get other events
|
||||||
|
// between the two MOVED_* events (including other MOVED_* ones).
|
||||||
|
//
|
||||||
|
// A second issue is that moving a file outside the watched directory will
|
||||||
|
// trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to
|
||||||
|
// read and delete it. So just storing it in a map would slowly leak memory.
|
||||||
|
//
|
||||||
|
// Doing it like this gives us a simple fast LRU-cache that won't allocate.
|
||||||
|
// Ten items should be more than enough for our purpose, and a loop over
|
||||||
|
// such a short array is faster than a map access anyway (not that it hugely
|
||||||
|
// matters since we're talking about hundreds of ns at the most, but still).
|
||||||
|
cookies [10]koekje
|
||||||
|
cookieIndex uint8
|
||||||
|
cookiesMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
watches struct {
|
||||||
|
wd map[uint32]*watch // wd → watch
|
||||||
|
path map[string]uint32 // pathname → wd
|
||||||
|
}
|
||||||
|
watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
path string // Watch path.
|
||||||
|
recurse bool // Recursion with ./...?
|
||||||
|
}
|
||||||
|
koekje struct {
|
||||||
|
cookie uint32
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatches() *watches {
|
||||||
|
return &watches{
|
||||||
|
wd: make(map[uint32]*watch),
|
||||||
|
path: make(map[string]uint32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] }
|
||||||
|
func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] }
|
||||||
|
func (w *watches) len() int { return len(w.wd) }
|
||||||
|
func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
|
||||||
|
func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) }
|
||||||
|
|
||||||
|
func (w *watches) removePath(path string) ([]uint32, error) {
|
||||||
|
path, recurse := recursivePath(path)
|
||||||
|
wd, ok := w.path[path]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch := w.wd[wd]
|
||||||
|
if recurse && !watch.recurse {
|
||||||
|
return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.path, path)
|
||||||
|
delete(w.wd, wd)
|
||||||
|
if !watch.recurse {
|
||||||
|
return []uint32{wd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
wds := make([]uint32, 0, 8)
|
||||||
|
wds = append(wds, wd)
|
||||||
|
for p, rwd := range w.path {
|
||||||
|
if strings.HasPrefix(p, path) {
|
||||||
|
delete(w.path, p)
|
||||||
|
delete(w.wd, rwd)
|
||||||
|
wds = append(wds, rwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
||||||
|
var existing *watch
|
||||||
|
wd, ok := w.path[path]
|
||||||
|
if ok {
|
||||||
|
existing = w.wd[wd]
|
||||||
|
}
|
||||||
|
|
||||||
|
upd, err := f(existing)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if upd != nil {
|
||||||
|
w.wd[upd.wd] = upd
|
||||||
|
w.path[upd.path] = upd.wd
|
||||||
|
|
||||||
|
if upd.wd != wd {
|
||||||
|
delete(w.wd, wd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
|
||||||
|
// I/O operations won't terminate on close.
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &inotify{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
fd: fd,
|
||||||
|
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||||
|
watches: newWatches(),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Causes any blocking reads to return with an error, provided the file
|
||||||
|
// still supports deadline operations.
|
||||||
|
err := w.inotifyFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
<-w.doneResp // Wait for readEvents() to finish.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
add := func(path string, with withOpts, recurse bool) error {
|
||||||
|
var flags uint32
|
||||||
|
if with.noFollow {
|
||||||
|
flags |= unix.IN_DONT_FOLLOW
|
||||||
|
}
|
||||||
|
if with.op.Has(Create) {
|
||||||
|
flags |= unix.IN_CREATE
|
||||||
|
}
|
||||||
|
if with.op.Has(Write) {
|
||||||
|
flags |= unix.IN_MODIFY
|
||||||
|
}
|
||||||
|
if with.op.Has(Remove) {
|
||||||
|
flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
}
|
||||||
|
if with.op.Has(Rename) {
|
||||||
|
flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
|
||||||
|
}
|
||||||
|
if with.op.Has(Chmod) {
|
||||||
|
flags |= unix.IN_ATTRIB
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableOpen) {
|
||||||
|
flags |= unix.IN_OPEN
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableRead) {
|
||||||
|
flags |= unix.IN_ACCESS
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableCloseWrite) {
|
||||||
|
flags |= unix.IN_CLOSE_WRITE
|
||||||
|
}
|
||||||
|
if with.op.Has(xUnportableCloseRead) {
|
||||||
|
flags |= unix.IN_CLOSE_NOWRITE
|
||||||
|
}
|
||||||
|
return w.register(path, flags, recurse)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
path, recurse := recursivePath(path)
|
||||||
|
if recurse {
|
||||||
|
return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
if root == path {
|
||||||
|
return fmt.Errorf("fsnotify: not a directory: %q", path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a Create event when adding new directory from a recursive
|
||||||
|
// watch; this is for "mkdir -p one/two/three". Usually all those
|
||||||
|
// directories will be created before we can set up watchers on the
|
||||||
|
// subdirectories, so only "one" would be sent as a Create event and
|
||||||
|
// not "one/two" and "one/two/three" (inotifywait -r has the same
|
||||||
|
// problem).
|
||||||
|
if with.sendCreate && root != path {
|
||||||
|
w.sendEvent(Event{Name: root, Op: Create})
|
||||||
|
}
|
||||||
|
|
||||||
|
return add(root, with, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return add(path, with, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||||
|
return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
|
||||||
|
if existing != nil {
|
||||||
|
flags |= existing.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := unix.InotifyAddWatch(w.fd, path, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := w.watches.wd[uint32(wd)]; ok {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing == nil {
|
||||||
|
return &watch{
|
||||||
|
wd: uint32(wd),
|
||||||
|
path: path,
|
||||||
|
flags: flags,
|
||||||
|
recurse: recurse,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.wd = uint32(wd)
|
||||||
|
existing.flags = flags
|
||||||
|
return existing, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.remove(filepath.Clean(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) remove(name string) error {
|
||||||
|
wds, err := w.watches.removePath(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, wd := range wds {
|
||||||
|
_, err := unix.InotifyRmWatch(w.fd, wd)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every
|
||||||
|
// case; the only two possible errors are:
|
||||||
|
//
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of
|
||||||
|
// any kind.
|
||||||
|
//
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is
|
||||||
|
// not a valid watch descriptor. Watch descriptors are invalidated
|
||||||
|
// when they are removed explicitly or implicitly; explicitly by
|
||||||
|
// inotify_rm_watch, implicitly when the file they are watching is
|
||||||
|
// deleted.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
entries := make([]string, 0, w.watches.len())
|
||||||
|
for pathname := range w.watches.path {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *inotify) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.doneResp)
|
||||||
|
close(w.Errors)
|
||||||
|
close(w.Events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
for {
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.inotifyFile.Read(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
err := errors.New("notify: short read in readEvents()") // Read was too short.
|
||||||
|
if n == 0 {
|
||||||
|
err = io.EOF // If EOF is received. This should really never happen.
|
||||||
|
}
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't know how many events we just read into the buffer While the
|
||||||
|
// offset points to at least one whole event.
|
||||||
|
var offset uint32
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point to the event in the buffer.
|
||||||
|
inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
if !w.sendError(ErrEventOverflow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev, ok := w.handleEvent(inEvent, &buf, offset)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !w.sendEvent(ev) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + inEvent.Len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
/// If the event happened to the watched directory or the watched file, the
|
||||||
|
/// kernel doesn't append the filename to the event, but we would like to
|
||||||
|
/// always fill the the "Name" field with a valid filename. We retrieve the
|
||||||
|
/// path of the watch from the "paths" map.
|
||||||
|
///
|
||||||
|
/// Can be nil if Remove() was called in another goroutine for this path
|
||||||
|
/// inbetween reading the events from the kernel and reading the internal
|
||||||
|
/// state. Not much we can do about it, so just skip. See #616.
|
||||||
|
watch := w.watches.byWd(uint32(inEvent.Wd))
|
||||||
|
if watch == nil {
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
name = watch.path
|
||||||
|
nameLen = uint32(inEvent.Len)
|
||||||
|
)
|
||||||
|
if nameLen > 0 {
|
||||||
|
/// Point "bytes" at the first byte of the filename
|
||||||
|
bb := *buf
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||||
|
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00")
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(name, inEvent.Mask, inEvent.Cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 {
|
||||||
|
w.watches.remove(watch)
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// inotify will automatically remove the watch on deletes; just need
|
||||||
|
// to clean our state here.
|
||||||
|
if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
w.watches.remove(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't really update the state when a watched path is moved; only
|
||||||
|
// IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch.
|
||||||
|
if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||||
|
if watch.recurse { // Do nothing
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.remove(watch.path)
|
||||||
|
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return Event{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip if we're watching both this path and the parent; the parent will
|
||||||
|
/// already send a delete so no need to do it twice.
|
||||||
|
if inEvent.Mask&unix.IN_DELETE_SELF != 0 {
|
||||||
|
_, ok := w.watches.path[filepath.Dir(watch.path)]
|
||||||
|
if ok {
|
||||||
|
return Event{}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
|
||||||
|
// Need to update watch path for recurse.
|
||||||
|
if watch.recurse {
|
||||||
|
isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||||
|
/// New directory created: set up watch on it.
|
||||||
|
if isDir && ev.Has(Create) {
|
||||||
|
err := w.register(ev.Name, watch.flags, true)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return Event{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This was a directory rename, so we need to update all the
|
||||||
|
// children.
|
||||||
|
//
|
||||||
|
// TODO: this is of course pretty slow; we should use a better data
|
||||||
|
// structure for storing all of this, e.g. store children in the
|
||||||
|
// watch. I have some code for this in my kqueue refactor we can use
|
||||||
|
// in the future. For now I'm okay with this as it's not publicly
|
||||||
|
// available. Correctness first, performance second.
|
||||||
|
if ev.renamedFrom != "" {
|
||||||
|
for k, ww := range w.watches.wd {
|
||||||
|
if k == watch.wd || ww.path == ev.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||||
|
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||||
|
w.watches.wd[k] = ww
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ev, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) isRecursive(path string) bool {
|
||||||
|
ww := w.watches.byPath(path)
|
||||||
|
if ww == nil { // path could be a file, so also check the Dir.
|
||||||
|
ww = w.watches.byPath(filepath.Dir(path))
|
||||||
|
}
|
||||||
|
return ww != nil && ww.recurse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_OPEN == unix.IN_OPEN {
|
||||||
|
e.Op |= xUnportableOpen
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ACCESS == unix.IN_ACCESS {
|
||||||
|
e.Op |= xUnportableRead
|
||||||
|
}
|
||||||
|
if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE {
|
||||||
|
e.Op |= xUnportableCloseWrite
|
||||||
|
}
|
||||||
|
if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE {
|
||||||
|
e.Op |= xUnportableCloseRead
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
|
||||||
|
if cookie != 0 {
|
||||||
|
if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
w.cookiesMu.Lock()
|
||||||
|
w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name}
|
||||||
|
w.cookieIndex++
|
||||||
|
if w.cookieIndex > 9 {
|
||||||
|
w.cookieIndex = 0
|
||||||
|
}
|
||||||
|
w.cookiesMu.Unlock()
|
||||||
|
} else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
w.cookiesMu.Lock()
|
||||||
|
var prev string
|
||||||
|
for _, c := range w.cookies {
|
||||||
|
if c.cookie == cookie {
|
||||||
|
prev = c.path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.cookiesMu.Unlock()
|
||||||
|
e.renamedFrom = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) xSupports(op Op) bool {
|
||||||
|
return true // Supports everything.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *inotify) state() {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
for wd, ww := range w.watches.wd {
|
||||||
|
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
705
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
705
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kqueue struct {
|
||||||
|
*shared
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
closepipe [2]int // Pipe used for closing kq.
|
||||||
|
watches *watches
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
watches struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
wd map[int]watch // wd → watch
|
||||||
|
path map[string]int // pathname → wd
|
||||||
|
byDir map[string]map[int]struct{} // dirname(path) → wd
|
||||||
|
seen map[string]struct{} // Keep track of if we know this file exists.
|
||||||
|
byUser map[string]struct{} // Watches added with Watcher.Add()
|
||||||
|
}
|
||||||
|
watch struct {
|
||||||
|
wd int
|
||||||
|
name string
|
||||||
|
linkName string // In case of links; name is the target, and this is the link.
|
||||||
|
isDir bool
|
||||||
|
dirFlags uint32
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatches() *watches {
|
||||||
|
return &watches{
|
||||||
|
wd: make(map[int]watch),
|
||||||
|
path: make(map[string]int),
|
||||||
|
byDir: make(map[string]map[int]struct{}),
|
||||||
|
seen: make(map[string]struct{}),
|
||||||
|
byUser: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) listPaths(userOnly bool) []string {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
|
||||||
|
if userOnly {
|
||||||
|
l := make([]string, 0, len(w.byUser))
|
||||||
|
for p := range w.byUser {
|
||||||
|
l = append(l, p)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
l := make([]string, 0, len(w.path))
|
||||||
|
for p := range w.path {
|
||||||
|
l = append(l, p)
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) watchesInDir(path string) []string {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
|
||||||
|
l := make([]string, 0, 4)
|
||||||
|
for fd := range w.byDir[path] {
|
||||||
|
info := w.wd[fd]
|
||||||
|
if _, ok := w.byUser[info.name]; !ok {
|
||||||
|
l = append(l, info.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark path as added by the user.
|
||||||
|
func (w *watches) addUserWatch(path string) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
w.byUser[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) addLink(path string, fd int) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
w.path[path] = fd
|
||||||
|
w.seen[path] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) add(path, linkPath string, fd int, isDir bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
w.path[path] = fd
|
||||||
|
w.wd[fd] = watch{wd: fd, name: path, linkName: linkPath, isDir: isDir}
|
||||||
|
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
byDir, ok := w.byDir[parent]
|
||||||
|
if !ok {
|
||||||
|
byDir = make(map[int]struct{}, 1)
|
||||||
|
w.byDir[parent] = byDir
|
||||||
|
}
|
||||||
|
byDir[fd] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byWd(fd int) (watch, bool) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
info, ok := w.wd[fd]
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) byPath(path string) (watch, bool) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
info, ok := w.wd[w.path[path]]
|
||||||
|
return info, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) updateDirFlags(path string, flags uint32) bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
fd, ok := w.path[path]
|
||||||
|
if !ok { // Already deleted: don't re-set it here.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
info := w.wd[fd]
|
||||||
|
info.dirFlags = flags
|
||||||
|
w.wd[fd] = info
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) remove(fd int, path string) bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
isDir := w.wd[fd].isDir
|
||||||
|
delete(w.path, path)
|
||||||
|
delete(w.byUser, path)
|
||||||
|
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
delete(w.byDir[parent], fd)
|
||||||
|
|
||||||
|
if len(w.byDir[parent]) == 0 {
|
||||||
|
delete(w.byDir, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.wd, fd)
|
||||||
|
delete(w.seen, path)
|
||||||
|
return isDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) markSeen(path string, exists bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if exists {
|
||||||
|
w.seen[path] = struct{}{}
|
||||||
|
} else {
|
||||||
|
delete(w.seen, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watches) seenBefore(path string) bool {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
_, ok := w.seen[path]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
kq, closepipe, err := newKqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &kqueue{
|
||||||
|
shared: newShared(ev, errs),
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
kq: kq,
|
||||||
|
closepipe: closepipe,
|
||||||
|
watches: newWatches(),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
//
|
||||||
|
// This registers a new event on closepipe, which will trigger an event when
|
||||||
|
// it's closed. This way we can use kevent() without timeout/polling; without
|
||||||
|
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
||||||
|
// all.
|
||||||
|
func newKqueue() (kq int, closepipe [2]int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the close pipe.
|
||||||
|
err = unix.Pipe(closepipe[:])
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(kq)
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
unix.CloseOnExec(closepipe[0])
|
||||||
|
unix.CloseOnExec(closepipe[1])
|
||||||
|
|
||||||
|
// Register changes to listen on the closepipe.
|
||||||
|
changes := make([]unix.Kevent_t, 1)
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
||||||
|
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
||||||
|
|
||||||
|
ok, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if ok == -1 {
|
||||||
|
unix.Close(kq)
|
||||||
|
unix.Close(closepipe[0])
|
||||||
|
unix.Close(closepipe[1])
|
||||||
|
return kq, closepipe, err
|
||||||
|
}
|
||||||
|
return kq, closepipe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Close() error {
|
||||||
|
if w.shared.close() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathsToRemove := w.watches.listPaths(false)
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *kqueue) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := w.addWatch(name, noteAllEvents, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.watches.addUserWatch(name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) Remove(name string) error {
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), name)
|
||||||
|
}
|
||||||
|
return w.remove(name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) remove(name string, unwatchFiles bool) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
info, ok := w.watches.byPath(name)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{info.wd}, unix.EV_DELETE, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(info.wd)
|
||||||
|
|
||||||
|
isDir := w.watches.remove(info.wd, name)
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if unwatchFiles && isDir {
|
||||||
|
pathsToRemove := w.watches.watchesInDir(name)
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error to
|
||||||
|
// the user, as that will just confuse them with an error about a
|
||||||
|
// path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.watches.listPaths(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set; the flags are interpreted as
|
||||||
|
// described in kevent(2).
|
||||||
|
//
|
||||||
|
// Returns the real path to the file which was added, with symlinks resolved.
|
||||||
|
func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, error) {
|
||||||
|
if w.isClosed() {
|
||||||
|
return "", ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
info, alreadyWatching := w.watches.byPath(name)
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets or named pipes.
|
||||||
|
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow symlinks, but only for paths added with Add(), and not paths
|
||||||
|
// we're adding from internalWatch from a listdir.
|
||||||
|
if !listDir && fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
link, err := os.Readlink(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(link) {
|
||||||
|
link = filepath.Join(filepath.Dir(name), link)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alreadyWatching = w.watches.byPath(link)
|
||||||
|
if alreadyWatching {
|
||||||
|
// Add to watches so we don't get spurious Create events later
|
||||||
|
// on when we diff the directories.
|
||||||
|
w.watches.addLink(name, 0)
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info.linkName = name
|
||||||
|
name = link
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||||
|
// See #354, and Go issues 11180 and 39237.
|
||||||
|
for {
|
||||||
|
info.wd, err = unix.Open(name, openMode, 0)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, unix.EINTR) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
info.isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.register([]int{info.wd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(info.wd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.watches.add(name, info.linkName, info.wd, info.isDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch the directory if it has not been watched before, or if it was
|
||||||
|
// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
if info.isDir {
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (info.dirFlags&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
if !w.watches.updateDirFlags(name, flags) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
d := name
|
||||||
|
if info.linkName != "" {
|
||||||
|
d = info.linkName
|
||||||
|
}
|
||||||
|
if err := w.watchDirectoryFiles(d); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *kqueue) readEvents() {
|
||||||
|
defer func() {
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
_ = unix.Close(w.kq)
|
||||||
|
unix.Close(w.closepipe[0])
|
||||||
|
}()
|
||||||
|
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
for {
|
||||||
|
kevents, err := w.read(eventBuffer)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kevent := range kevents {
|
||||||
|
var (
|
||||||
|
wd = int(kevent.Ident)
|
||||||
|
mask = uint32(kevent.Fflags)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shut down the loop when the pipe is closed, but only after all
|
||||||
|
// other events have been processed.
|
||||||
|
if wd == w.closepipe[0] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path, ok := w.watches.byWd(wd)
|
||||||
|
if debug {
|
||||||
|
internal.Debug(path.name, &kevent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS it seems that sometimes an event with Ident=0 is
|
||||||
|
// delivered, and no other flags/information beyond that, even
|
||||||
|
// though we never saw such a file descriptor. For example in
|
||||||
|
// TestWatchSymlink/277 (usually at the end, but sometimes sooner):
|
||||||
|
//
|
||||||
|
// fmt.Printf("READ: %2d %#v\n", kevent.Ident, kevent)
|
||||||
|
// unix.Kevent_t{Ident:0x2a, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||||||
|
// unix.Kevent_t{Ident:0x0, Filter:-4, Flags:0x25, Fflags:0x2, Data:0, Udata:(*uint8)(nil)}
|
||||||
|
//
|
||||||
|
// The first is a normal event, the second with Ident 0. No error
|
||||||
|
// flag, no data, no ... nothing.
|
||||||
|
//
|
||||||
|
// I read a bit through bsd/kern_event.c from the xnu source, but I
|
||||||
|
// don't really see an obvious location where this is triggered –
|
||||||
|
// this doesn't seem intentional, but idk...
|
||||||
|
//
|
||||||
|
// Technically fd 0 is a valid descriptor, so only skip it if
|
||||||
|
// there's no path, and if we're on macOS.
|
||||||
|
if !ok && kevent.Ident == 0 && runtime.GOOS == "darwin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(path.name, path.linkName, mask)
|
||||||
|
|
||||||
|
if event.Has(Rename) || event.Has(Remove) {
|
||||||
|
w.remove(event.Name, false)
|
||||||
|
w.watches.markSeen(event.Name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
||||||
|
w.dirChange(event.Name)
|
||||||
|
} else if !w.sendEvent(event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Has(Remove) {
|
||||||
|
// Look for a file that may have overwritten this; for example,
|
||||||
|
// mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
_, found := w.watches.byPath(fileDir)
|
||||||
|
if found {
|
||||||
|
// TODO: this branch is never triggered in any test.
|
||||||
|
// Added in d6220df (2012).
|
||||||
|
// isDir check added in 8611c35 (2016): https://github.com/fsnotify/fsnotify/pull/111
|
||||||
|
//
|
||||||
|
// I don't really get how this can be triggered either.
|
||||||
|
// And it wasn't triggered in the patch that added it,
|
||||||
|
// either.
|
||||||
|
//
|
||||||
|
// Original also had a comment:
|
||||||
|
// make sure the directory exists before we watch for
|
||||||
|
// changes. When we do a recursive watch and perform
|
||||||
|
// rm -rf, the parent directory might have gone
|
||||||
|
// missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the
|
||||||
|
// parent directory.
|
||||||
|
err := w.dirChange(fileDir)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path := filepath.Clean(event.Name)
|
||||||
|
if fi, err := os.Lstat(path); err == nil {
|
||||||
|
err := w.sendCreateIfNew(path, fi)
|
||||||
|
if !w.sendError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func (w *kqueue) newEvent(name, linkName string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if linkName != "" {
|
||||||
|
// If the user watched "/path/link" then emit events as "/path/link"
|
||||||
|
// rather than "/path/target".
|
||||||
|
e.Name = linkName
|
||||||
|
}
|
||||||
|
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
// No point sending a write and delete event at the same time: if it's gone,
|
||||||
|
// then it's gone.
|
||||||
|
if e.Op.Has(Write) && e.Op.Has(Remove) {
|
||||||
|
e.Op &^= Write
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *kqueue) watchDirectoryFiles(dirPath string) error {
|
||||||
|
files, err := os.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
path := filepath.Join(dirPath, f.Name())
|
||||||
|
|
||||||
|
fi, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPath, err := w.internalWatch(path, fi)
|
||||||
|
if err != nil {
|
||||||
|
// No permission to read the file; that's not a problem: just skip.
|
||||||
|
// But do add it to w.fileExists to prevent it from being picked up
|
||||||
|
// as a "new" file later (it still shows up in the directory
|
||||||
|
// listing).
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||||
|
cleanPath = filepath.Clean(path)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%q: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.watches.markSeen(cleanPath, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the directory for new files and send an event for them.
|
||||||
|
//
|
||||||
|
// This functionality is to have the BSD watcher match the inotify, which sends
|
||||||
|
// a create event for files created in a watched directory.
|
||||||
|
func (w *kqueue) dirChange(dir string) error {
|
||||||
|
files, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
// Directory no longer exists: we can ignore this safely. kqueue will
|
||||||
|
// still give us the correct events.
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fsnotify.dirChange %q: %w", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
fi, err := f.Info()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.sendCreateIfNew(filepath.Join(dir, fi.Name()), fi)
|
||||||
|
if err != nil {
|
||||||
|
// Don't need to send an error if this file isn't readable.
|
||||||
|
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fsnotify.dirChange: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a create event if the file isn't already being tracked, and start
|
||||||
|
// watching this file.
|
||||||
|
func (w *kqueue) sendCreateIfNew(path string, fi os.FileInfo) error {
|
||||||
|
if !w.watches.seenBefore(path) {
|
||||||
|
if !w.sendEvent(Event{Name: path, Op: Create}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like watchDirectoryFiles, but without doing another ReadDir.
|
||||||
|
path, err := w.internalWatch(path, fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.watches.markSeen(path, true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) internalWatch(name string, fi os.FileInfo) (string, error) {
|
||||||
|
if fi.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories, but preserve
|
||||||
|
// the flags used if currently watching subdirectory
|
||||||
|
info, _ := w.watches.byPath(name)
|
||||||
|
return w.addWatch(name, info.dirFlags|unix.NOTE_DELETE|unix.NOTE_RENAME, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch file to mimic Linux inotify.
|
||||||
|
return w.addWatch(name, noteAllEvents, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register events with the queue.
|
||||||
|
func (w *kqueue) register(fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types.
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the events.
|
||||||
|
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
func (w *kqueue) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(w.kq, nil, events, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *kqueue) xSupports(op Op) bool {
|
||||||
|
//if runtime.GOOS == "freebsd" {
|
||||||
|
// return true // Supports everything.
|
||||||
|
//}
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
22
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
22
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type other struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 0
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
return nil, errors.New("fsnotify not supported on the current platform")
|
||||||
|
}
|
||||||
|
func (w *other) Close() error { return nil }
|
||||||
|
func (w *other) WatchList() []string { return nil }
|
||||||
|
func (w *other) Add(name string) error { return nil }
|
||||||
|
func (w *other) AddWith(name string, opts ...addOpt) error { return nil }
|
||||||
|
func (w *other) Remove(name string) error { return nil }
|
||||||
|
func (w *other) xSupports(op Op) bool { return false }
|
||||||
680
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
680
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Windows backend based on ReadDirectoryChangesW()
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify/internal"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readDirChangesW struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
|
||||||
|
port windows.Handle // Handle to completion port
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
done chan chan<- error
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watches, closed
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
closed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultBufferSize = 50
|
||||||
|
|
||||||
|
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||||
|
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
w := &readDirChangesW{
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
done: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) isClosed() bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) sendEvent(name, renamedFrom string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
event := w.newEvent(name, uint32(mask))
|
||||||
|
event.renamedFrom = renamedFrom
|
||||||
|
select {
|
||||||
|
case ch := <-w.done:
|
||||||
|
w.done <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *readDirChangesW) sendError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.closed = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Send "done" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.done <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Add(name string) error { return w.AddWith(name) }
|
||||||
|
|
||||||
|
func (w *readDirChangesW) AddWith(name string, opts ...addOpt) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
with := getOptions(opts...)
|
||||||
|
if !w.xSupports(with.op) {
|
||||||
|
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||||
|
}
|
||||||
|
if with.bufsize < 4096 {
|
||||||
|
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
bufsize: with.bufsize,
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) Remove(name string) error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) WatchList() []string {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for _, entry := range w.watches {
|
||||||
|
for _, watchEntry := range entry {
|
||||||
|
for name := range watchEntry.names {
|
||||||
|
entries = append(entries, filepath.Join(watchEntry.path, name))
|
||||||
|
}
|
||||||
|
// the directory itself is being watched
|
||||||
|
if watchEntry.mask != 0 {
|
||||||
|
entries = append(entries, watchEntry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
||||||
|
// add various options to the watch. This has long since been removed.
|
||||||
|
//
|
||||||
|
// The "sys" in the name is misleading as they're not part of any "system".
|
||||||
|
//
|
||||||
|
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
||||||
|
const (
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *readDirChangesW) newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
bufsize int
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle windows.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov windows.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
recurse bool // Recursive watch?
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf []byte // buffer, allocated later
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
indexMap map[uint64]*watch
|
||||||
|
watchMap map[uint32]indexMap
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *readDirChangesW) wakeupReader() error {
|
||||||
|
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
||||||
|
if err != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", err)
|
||||||
|
}
|
||||||
|
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) getIno(path string) (ino *inode, err error) {
|
||||||
|
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
||||||
|
windows.FILE_LIST_DIRECTORY,
|
||||||
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
||||||
|
nil, windows.OPEN_EXISTING,
|
||||||
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fi windows.ByHandleFileInformation
|
||||||
|
err = windows.GetFileInformationByHandle(h, &fi)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) error {
|
||||||
|
pathname, recurse := recursivePath(pathname)
|
||||||
|
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", err)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
recurse: recurse,
|
||||||
|
buf: make([]byte, bufsize),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
windows.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.startRead(watchEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) remWatch(pathname string) error {
|
||||||
|
pathname, recurse := recursivePath(pathname)
|
||||||
|
|
||||||
|
dir, err := w.getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := w.getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if recurse && !watch.recurse {
|
||||||
|
return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = windows.CloseHandle(ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *readDirChangesW) startRead(watch *watch) error {
|
||||||
|
err := windows.CancelIo(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CancelIo", err))
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := w.toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= w.toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
err := windows.CloseHandle(watch.ino.handle)
|
||||||
|
if err != nil {
|
||||||
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to pass the array, rather than the slice.
|
||||||
|
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
|
||||||
|
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
|
||||||
|
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
|
||||||
|
watch.recurse, mask, nil, &watch.ov, 0)
|
||||||
|
if rdErr != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
||||||
|
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *readDirChangesW) readEvents() {
|
||||||
|
var (
|
||||||
|
n uint32
|
||||||
|
key uintptr
|
||||||
|
ov *windows.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// This error is handled after the watch == nil check below.
|
||||||
|
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
||||||
|
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.done:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.CloseHandle(w.port)
|
||||||
|
if err != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", err)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch qErr {
|
||||||
|
case nil:
|
||||||
|
// No error
|
||||||
|
case windows.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case windows.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, "", watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case windows.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.sendError(ErrEventOverflow)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
|
||||||
|
// Create a buf that is the size of the path name
|
||||||
|
size := int(raw.FileNameLength / 2)
|
||||||
|
var buf []uint16
|
||||||
|
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||||
|
sh.Len = size
|
||||||
|
sh.Cap = size
|
||||||
|
name := windows.UTF16ToString(buf)
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
internal.Debug(fullname, raw.Action)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
// Update saved path of all sub-watches.
|
||||||
|
old := filepath.Join(watch.path, watch.rename)
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, watchMap := range w.watches {
|
||||||
|
for _, ww := range watchMap {
|
||||||
|
if strings.HasPrefix(ww.path, old) {
|
||||||
|
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(fullname, "", watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(fullname, filepath.Join(watch.path, watch.rename), watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
} else {
|
||||||
|
w.sendEvent(fullname, "", watch.mask&w.toFSnotifyFlags(raw.Action))
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, watch.rename), "", watch.names[name]&mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
//lint:ignore ST1005 Windows should be capitalized
|
||||||
|
w.sendError(errors.New("Windows system assumed buffer larger than it is, events have likely been missed"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.sendError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case windows.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case windows.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case windows.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *readDirChangesW) xSupports(op Op) bool {
|
||||||
|
if op.Has(xUnportableOpen) || op.Has(xUnportableRead) ||
|
||||||
|
op.Has(xUnportableCloseWrite) || op.Has(xUnportableCloseRead) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
496
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
496
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
// Package fsnotify provides a cross-platform interface for file system
|
||||||
|
// notifications.
|
||||||
|
//
|
||||||
|
// Currently supported systems:
|
||||||
|
//
|
||||||
|
// - Linux via inotify
|
||||||
|
// - BSD, macOS via kqueue
|
||||||
|
// - Windows via ReadDirectoryChangesW
|
||||||
|
// - illumos via FEN
|
||||||
|
//
|
||||||
|
// # FSNOTIFY_DEBUG
|
||||||
|
//
|
||||||
|
// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
|
||||||
|
// stderr. This can be useful to track down some problems, especially in cases
|
||||||
|
// where fsnotify is used as an indirect dependency.
|
||||||
|
//
|
||||||
|
// Every event will be printed as soon as there's something useful to print,
|
||||||
|
// with as little processing from fsnotify.
|
||||||
|
//
|
||||||
|
// Example output:
|
||||||
|
//
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1"
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1"
|
||||||
|
// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1"
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of paths, delivering events on a channel.
|
||||||
|
//
|
||||||
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
||||||
|
// value).
|
||||||
|
//
|
||||||
|
// # Linux notes
|
||||||
|
//
|
||||||
|
// When a file is removed a Remove event won't be emitted until all file
|
||||||
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
||||||
|
//
|
||||||
|
// fp := os.Open("file")
|
||||||
|
// os.Remove("file") // Triggers Chmod
|
||||||
|
// fp.Close() // Triggers Remove
|
||||||
|
//
|
||||||
|
// This is the event that inotify sends, so not much can be changed about this.
|
||||||
|
//
|
||||||
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||||
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||||
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||||
|
// create is an "instance", and every path you add is a "watch".
|
||||||
|
//
|
||||||
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||||
|
// /proc/sys/fs/inotify/max_user_instances
|
||||||
|
//
|
||||||
|
// To increase them you can use sysctl or write the value to the /proc file:
|
||||||
|
//
|
||||||
|
// # Default values on Linux 5.18
|
||||||
|
// sysctl fs.inotify.max_user_watches=124983
|
||||||
|
// sysctl fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||||
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||||
|
// your distro's documentation):
|
||||||
|
//
|
||||||
|
// fs.inotify.max_user_watches=124983
|
||||||
|
// fs.inotify.max_user_instances=128
|
||||||
|
//
|
||||||
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||||
|
// files" error.
|
||||||
|
//
|
||||||
|
// # kqueue notes (macOS, BSD)
|
||||||
|
//
|
||||||
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
||||||
|
// so if you're watching a directory with five files then that's six file
|
||||||
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
||||||
|
// these platforms.
|
||||||
|
//
|
||||||
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
||||||
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
||||||
|
// systems.
|
||||||
|
//
|
||||||
|
// # Windows notes
|
||||||
|
//
|
||||||
|
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
|
||||||
|
// ("C:/path/to/dir") will also work.
|
||||||
|
//
|
||||||
|
// When a watched directory is removed it will always send an event for the
|
||||||
|
// directory itself, but may not send events for all files in that directory.
|
||||||
|
// Sometimes it will send events for all files, sometimes it will send no
|
||||||
|
// events, and often only for some files.
|
||||||
|
//
|
||||||
|
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
||||||
|
// value that is guaranteed to work with SMB filesystems. If you have many
|
||||||
|
// events in quick succession this may not be enough, and you will have to use
|
||||||
|
// [WithBufferSize] to increase the value.
|
||||||
|
type Watcher struct {
|
||||||
|
b backend
|
||||||
|
|
||||||
|
// Events sends the filesystem change events.
|
||||||
|
//
|
||||||
|
// fsnotify can send the following events; a "path" here can refer to a
|
||||||
|
// file, directory, symbolic link, or special file like a FIFO.
|
||||||
|
//
|
||||||
|
// fsnotify.Create A new path was created; this may be followed by one
|
||||||
|
// or more Write events if data also gets written to a
|
||||||
|
// file.
|
||||||
|
//
|
||||||
|
// fsnotify.Remove A path was removed.
|
||||||
|
//
|
||||||
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
||||||
|
// old path as Event.Name, and a Create event will be
|
||||||
|
// sent with the new name. Renames are only sent for
|
||||||
|
// paths that are currently watched; e.g. moving an
|
||||||
|
// unmonitored file into a monitored directory will
|
||||||
|
// show up as just a Create. Similarly, renaming a file
|
||||||
|
// to outside a monitored directory will show up as
|
||||||
|
// only a Rename.
|
||||||
|
//
|
||||||
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
||||||
|
// also trigger a Write. A single "write action"
|
||||||
|
// initiated by the user may show up as one or multiple
|
||||||
|
// writes, depending on when the system syncs things to
|
||||||
|
// disk. For example when compiling a large Go program
|
||||||
|
// you may get hundreds of Write events, and you may
|
||||||
|
// want to wait until you've stopped receiving them
|
||||||
|
// (see the dedup example in cmd/fsnotify).
|
||||||
|
//
|
||||||
|
// Some systems may send Write event for directories
|
||||||
|
// when the directory content changes.
|
||||||
|
//
|
||||||
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||||
|
// when a file is removed (or more accurately, when a
|
||||||
|
// link to an inode is removed). On kqueue it's sent
|
||||||
|
// when a file is truncated. On Windows it's never
|
||||||
|
// sent.
|
||||||
|
Events chan Event
|
||||||
|
|
||||||
|
// Errors sends any errors.
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event represents a file system notification.
|
||||||
|
type Event struct {
|
||||||
|
// Path to the file or directory.
|
||||||
|
//
|
||||||
|
// Paths are relative to the input; for example with Add("dir") the Name
|
||||||
|
// will be set to "dir/file" if you create that file, but if you use
|
||||||
|
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// File operation that triggered the event.
|
||||||
|
//
|
||||||
|
// This is a bitmask and some systems may send multiple operations at once.
|
||||||
|
// Use the Event.Has() method instead of comparing with ==.
|
||||||
|
Op Op
|
||||||
|
|
||||||
|
// Create events will have this set to the old path if it's a rename. This
|
||||||
|
// only works when both the source and destination are watched. It's not
|
||||||
|
// reliable when watching individual files, only directories.
|
||||||
|
//
|
||||||
|
// For example "mv /tmp/file /tmp/rename" will emit:
|
||||||
|
//
|
||||||
|
// Event{Op: Rename, Name: "/tmp/file"}
|
||||||
|
// Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
|
||||||
|
renamedFrom string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
||||||
|
// full description, and check them with [Event.Has].
|
||||||
|
const (
|
||||||
|
// A new pathname was created.
|
||||||
|
Create Op = 1 << iota
|
||||||
|
|
||||||
|
// The pathname was written to; this does *not* mean the write has finished,
|
||||||
|
// and a write can be followed by more writes.
|
||||||
|
Write
|
||||||
|
|
||||||
|
// The path was removed; any watches on it will be removed. Some "remove"
|
||||||
|
// operations may trigger a Rename if the file is actually moved (for
|
||||||
|
// example "remove to trash" is often a rename).
|
||||||
|
Remove
|
||||||
|
|
||||||
|
// The path was renamed to something else; any watches on it will be
|
||||||
|
// removed.
|
||||||
|
Rename
|
||||||
|
|
||||||
|
// File attributes were changed.
|
||||||
|
//
|
||||||
|
// It's generally not recommended to take action on this event, as it may
|
||||||
|
// get triggered very frequently by some software. For example, Spotlight
|
||||||
|
// indexing on macOS, anti-virus software, backup software, etc.
|
||||||
|
Chmod
|
||||||
|
|
||||||
|
// File descriptor was opened.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableOpen
|
||||||
|
|
||||||
|
// File was read from.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableRead
|
||||||
|
|
||||||
|
// File opened for writing was closed.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
//
|
||||||
|
// The advantage of using this over Write is that it's more reliable than
|
||||||
|
// waiting for Write events to stop. It's also faster (if you're not
|
||||||
|
// listening to Write events): copying a file of a few GB can easily
|
||||||
|
// generate tens of thousands of Write events in a short span of time.
|
||||||
|
xUnportableCloseWrite
|
||||||
|
|
||||||
|
// File opened for reading was closed.
|
||||||
|
//
|
||||||
|
// Only works on Linux and FreeBSD.
|
||||||
|
xUnportableCloseRead
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNonExistentWatch is used when Remove() is called on a path that's not
|
||||||
|
// added.
|
||||||
|
ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
|
||||||
|
|
||||||
|
// ErrClosed is used when trying to operate on a closed Watcher.
|
||||||
|
ErrClosed = errors.New("fsnotify: watcher already closed")
|
||||||
|
|
||||||
|
// ErrEventOverflow is reported from the Errors channel when there are too
|
||||||
|
// many events:
|
||||||
|
//
|
||||||
|
// - inotify: inotify returns IN_Q_OVERFLOW – because there are too
|
||||||
|
// many queued events (the fs.inotify.max_queued_events
|
||||||
|
// sysctl can be used to increase this).
|
||||||
|
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
||||||
|
// - kqueue, fen: Not used.
|
||||||
|
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
|
||||||
|
|
||||||
|
// ErrUnsupported is returned by AddWith() when WithOps() specified an
|
||||||
|
// Unportable event that's not supported on this platform.
|
||||||
|
//lint:ignore ST1012 not relevant
|
||||||
|
xErrUnsupported = errors.New("fsnotify: not supported with this backend")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWatcher creates a new Watcher.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
ev, errs := make(chan Event, defaultBufferSize), make(chan error)
|
||||||
|
b, err := newBackend(ev, errs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Watcher{b: b, Events: ev, Errors: errs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
||||||
|
// channel.
|
||||||
|
//
|
||||||
|
// The main use case for this is situations with a very large number of events
|
||||||
|
// where the kernel buffer size can't be increased (e.g. due to lack of
|
||||||
|
// permissions). An unbuffered Watcher will perform better for almost all use
|
||||||
|
// cases, and whenever possible you will be better off increasing the kernel
|
||||||
|
// buffers instead of adding a large userspace buffer.
|
||||||
|
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
||||||
|
ev, errs := make(chan Event, sz), make(chan error)
|
||||||
|
b, err := newBackend(ev, errs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Watcher{b: b, Events: ev, Errors: errs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// A path can only be watched once; watching it more than once is a no-op and will
|
||||||
|
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
||||||
|
// watched.
|
||||||
|
//
|
||||||
|
// A watch will be automatically removed if the watched path is deleted or
|
||||||
|
// renamed. The exception is the Windows backend, which doesn't remove the
|
||||||
|
// watcher on renames.
|
||||||
|
//
|
||||||
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
||||||
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
||||||
|
//
|
||||||
|
// Returns [ErrClosed] if [Watcher.Close] was called.
|
||||||
|
//
|
||||||
|
// See [Watcher.AddWith] for a version that allows adding options.
|
||||||
|
//
|
||||||
|
// # Watching directories
|
||||||
|
//
|
||||||
|
// All files in a directory are monitored, including new files that are created
|
||||||
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
||||||
|
// non-recursive).
|
||||||
|
//
|
||||||
|
// # Watching files
|
||||||
|
//
|
||||||
|
// Watching individual files (rather than directories) is generally not
|
||||||
|
// recommended as many programs (especially editors) update files atomically: it
|
||||||
|
// will write to a temporary file which is then moved to destination,
|
||||||
|
// overwriting the original (or some variant thereof). The watcher on the
|
||||||
|
// original file is now lost, as that no longer exists.
|
||||||
|
//
|
||||||
|
// The upshot of this is that a power failure or crash won't leave a
|
||||||
|
// half-written file.
|
||||||
|
//
|
||||||
|
// Watch the parent directory and use Event.Name to filter out files you're not
|
||||||
|
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
||||||
|
func (w *Watcher) Add(path string) error { return w.b.Add(path) }
|
||||||
|
|
||||||
|
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
||||||
|
// the defaults described below are used.
|
||||||
|
//
|
||||||
|
// Possible options are:
|
||||||
|
//
|
||||||
|
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
||||||
|
// other platforms. The default is 64K (65536 bytes).
|
||||||
|
func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
|
||||||
|
|
||||||
|
// Remove stops monitoring the path for changes.
|
||||||
|
//
|
||||||
|
// Directories are always removed non-recursively. For example, if you added
|
||||||
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
||||||
|
//
|
||||||
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
||||||
|
//
|
||||||
|
// Returns nil if [Watcher.Close] was called.
|
||||||
|
func (w *Watcher) Remove(path string) error { return w.b.Remove(path) }
|
||||||
|
|
||||||
|
// Close removes all watches and closes the Events channel.
|
||||||
|
func (w *Watcher) Close() error { return w.b.Close() }
|
||||||
|
|
||||||
|
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
||||||
|
// yet removed).
|
||||||
|
//
|
||||||
|
// The order is undefined, and may differ per call. Returns nil if
|
||||||
|
// [Watcher.Close] was called.
|
||||||
|
func (w *Watcher) WatchList() []string { return w.b.WatchList() }
|
||||||
|
|
||||||
|
// Supports reports if all the listed operations are supported by this platform.
|
||||||
|
//
|
||||||
|
// Create, Write, Remove, Rename, and Chmod are always supported. It can only
|
||||||
|
// return false for an Op starting with Unportable.
|
||||||
|
func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
|
||||||
|
|
||||||
|
func (o Op) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
if o.Has(Create) {
|
||||||
|
b.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if o.Has(Remove) {
|
||||||
|
b.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if o.Has(Write) {
|
||||||
|
b.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableOpen) {
|
||||||
|
b.WriteString("|OPEN")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableRead) {
|
||||||
|
b.WriteString("|READ")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableCloseWrite) {
|
||||||
|
b.WriteString("|CLOSE_WRITE")
|
||||||
|
}
|
||||||
|
if o.Has(xUnportableCloseRead) {
|
||||||
|
b.WriteString("|CLOSE_READ")
|
||||||
|
}
|
||||||
|
if o.Has(Rename) {
|
||||||
|
b.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if o.Has(Chmod) {
|
||||||
|
b.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if b.Len() == 0 {
|
||||||
|
return "[no events]"
|
||||||
|
}
|
||||||
|
return b.String()[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has reports if this operation has the given operation.
|
||||||
|
func (o Op) Has(h Op) bool { return o&h != 0 }
|
||||||
|
|
||||||
|
// Has reports if this event has the given operation.
|
||||||
|
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
||||||
|
|
||||||
|
// String returns a string representation of the event with their path.
|
||||||
|
func (e Event) String() string {
|
||||||
|
if e.renamedFrom != "" {
|
||||||
|
return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
backend interface {
|
||||||
|
Add(string) error
|
||||||
|
AddWith(string, ...addOpt) error
|
||||||
|
Remove(string) error
|
||||||
|
WatchList() []string
|
||||||
|
Close() error
|
||||||
|
xSupports(Op) bool
|
||||||
|
}
|
||||||
|
addOpt func(opt *withOpts)
|
||||||
|
withOpts struct {
|
||||||
|
bufsize int
|
||||||
|
op Op
|
||||||
|
noFollow bool
|
||||||
|
sendCreate bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug = func() bool {
|
||||||
|
// Check for exactly "1" (rather than mere existence) so we can add
|
||||||
|
// options/flags in the future. I don't know if we ever want that, but it's
|
||||||
|
// nice to leave the option open.
|
||||||
|
return os.Getenv("FSNOTIFY_DEBUG") == "1"
|
||||||
|
}()
|
||||||
|
|
||||||
|
var defaultOpts = withOpts{
|
||||||
|
bufsize: 65536, // 64K
|
||||||
|
op: Create | Write | Remove | Rename | Chmod,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOptions(opts ...addOpt) withOpts {
|
||||||
|
with := defaultOpts
|
||||||
|
for _, o := range opts {
|
||||||
|
if o != nil {
|
||||||
|
o(&with)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return with
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
|
||||||
|
//
|
||||||
|
// This only has effect on Windows systems, and is a no-op for other backends.
|
||||||
|
//
|
||||||
|
// The default value is 64K (65536 bytes) which is the highest value that works
|
||||||
|
// on all filesystems and should be enough for most applications, but if you
|
||||||
|
// have a large burst of events it may not be enough. You can increase it if
|
||||||
|
// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
|
||||||
|
//
|
||||||
|
// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
||||||
|
func WithBufferSize(bytes int) addOpt {
|
||||||
|
return func(opt *withOpts) { opt.bufsize = bytes }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOps sets which operations to listen for. The default is [Create],
|
||||||
|
// [Write], [Remove], [Rename], and [Chmod].
|
||||||
|
//
|
||||||
|
// Excluding operations you're not interested in can save quite a bit of CPU
|
||||||
|
// time; in some use cases there may be hundreds of thousands of useless Write
|
||||||
|
// or Chmod operations per second.
|
||||||
|
//
|
||||||
|
// This can also be used to add unportable operations not supported by all
|
||||||
|
// platforms; unportable operations all start with "Unportable":
|
||||||
|
// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
|
||||||
|
// [UnportableCloseRead].
|
||||||
|
//
|
||||||
|
// AddWith returns an error when using an unportable operation that's not
|
||||||
|
// supported. Use [Watcher.Support] to check for support.
|
||||||
|
func withOps(op Op) addOpt {
|
||||||
|
return func(opt *withOpts) { opt.op = op }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoFollow disables following symlinks, so the symlinks themselves are
|
||||||
|
// watched.
|
||||||
|
func withNoFollow() addOpt {
|
||||||
|
return func(opt *withOpts) { opt.noFollow = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Internal" option for recursive watches on inotify.
|
||||||
|
func withCreate() addOpt {
|
||||||
|
return func(opt *withOpts) { opt.sendCreate = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
var enableRecurse = false
|
||||||
|
|
||||||
|
// Check if this path is recursive (ends with "/..." or "\..."), and return the
|
||||||
|
// path with the /... stripped.
|
||||||
|
func recursivePath(path string) (string, bool) {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
if !enableRecurse { // Only enabled in tests for now.
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
|
if filepath.Base(path) == "..." {
|
||||||
|
return filepath.Dir(path), true
|
||||||
|
}
|
||||||
|
return path, false
|
||||||
|
}
|
||||||
39
vendor/github.com/fsnotify/fsnotify/internal/darwin.go
generated
vendored
Normal file
39
vendor/github.com/fsnotify/fsnotify/internal/darwin.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = l.Cur
|
||||||
|
|
||||||
|
if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles {
|
||||||
|
maxfiles = uint64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles {
|
||||||
|
maxfiles = uint64(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||||
57
vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
generated
vendored
Normal file
57
vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE},
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_BACKGROUND", unix.NOTE_BACKGROUND},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_CRITICAL", unix.NOTE_CRITICAL},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS},
|
||||||
|
{"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR},
|
||||||
|
{"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL},
|
||||||
|
{"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL},
|
||||||
|
{"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK},
|
||||||
|
{"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY},
|
||||||
|
{"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_FUNLOCK", unix.NOTE_FUNLOCK},
|
||||||
|
{"NOTE_LEEWAY", unix.NOTE_LEEWAY},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_MACHTIME", unix.NOTE_MACHTIME},
|
||||||
|
{"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME},
|
||||||
|
{"NOTE_NONE", unix.NOTE_NONE},
|
||||||
|
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||||
|
{"NOTE_OOB", unix.NOTE_OOB},
|
||||||
|
//{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!)
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_REAP", unix.NOTE_REAP},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||||
|
{"NOTE_SIGNAL", unix.NOTE_SIGNAL},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||||
|
{"NOTE_VM_ERROR", unix.NOTE_VM_ERROR},
|
||||||
|
{"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE},
|
||||||
|
{"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE},
|
||||||
|
{"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
33
vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
generated
vendored
Normal file
33
vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_OOB", unix.NOTE_OOB},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
42
vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
generated
vendored
Normal file
42
vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ABSTIME", unix.NOTE_ABSTIME},
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_CLOSE", unix.NOTE_CLOSE},
|
||||||
|
{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||||
|
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||||
|
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||||
|
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||||
|
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||||
|
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||||
|
{"NOTE_FILE_POLL", unix.NOTE_FILE_POLL},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_MSECONDS", unix.NOTE_MSECONDS},
|
||||||
|
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||||
|
{"NOTE_OPEN", unix.NOTE_OPEN},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_READ", unix.NOTE_READ},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||||
|
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
32
vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
generated
vendored
Normal file
32
vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, kevent *unix.Kevent_t) {
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
|
||||||
|
}
|
||||||
56
vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
generated
vendored
Normal file
56
vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask, cookie uint32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"IN_ACCESS", unix.IN_ACCESS},
|
||||||
|
{"IN_ATTRIB", unix.IN_ATTRIB},
|
||||||
|
{"IN_CLOSE", unix.IN_CLOSE},
|
||||||
|
{"IN_CLOSE_NOWRITE", unix.IN_CLOSE_NOWRITE},
|
||||||
|
{"IN_CLOSE_WRITE", unix.IN_CLOSE_WRITE},
|
||||||
|
{"IN_CREATE", unix.IN_CREATE},
|
||||||
|
{"IN_DELETE", unix.IN_DELETE},
|
||||||
|
{"IN_DELETE_SELF", unix.IN_DELETE_SELF},
|
||||||
|
{"IN_IGNORED", unix.IN_IGNORED},
|
||||||
|
{"IN_ISDIR", unix.IN_ISDIR},
|
||||||
|
{"IN_MODIFY", unix.IN_MODIFY},
|
||||||
|
{"IN_MOVE", unix.IN_MOVE},
|
||||||
|
{"IN_MOVED_FROM", unix.IN_MOVED_FROM},
|
||||||
|
{"IN_MOVED_TO", unix.IN_MOVED_TO},
|
||||||
|
{"IN_MOVE_SELF", unix.IN_MOVE_SELF},
|
||||||
|
{"IN_OPEN", unix.IN_OPEN},
|
||||||
|
{"IN_Q_OVERFLOW", unix.IN_Q_OVERFLOW},
|
||||||
|
{"IN_UNMOUNT", unix.IN_UNMOUNT},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
var c string
|
||||||
|
if cookie > 0 {
|
||||||
|
c = fmt.Sprintf("(cookie: %d) ", cookie)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-30s → %s%q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), strings.Join(l, "|"), c, name)
|
||||||
|
}
|
||||||
25
vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
generated
vendored
Normal file
25
vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
28
vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
var names = []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||||
|
// {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386?
|
||||||
|
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||||
|
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||||
|
{"NOTE_EOF", unix.NOTE_EOF},
|
||||||
|
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||||
|
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||||
|
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||||
|
{"NOTE_FORK", unix.NOTE_FORK},
|
||||||
|
{"NOTE_LINK", unix.NOTE_LINK},
|
||||||
|
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||||
|
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||||
|
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||||
|
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||||
|
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||||
|
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||||
|
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||||
|
{"NOTE_TRUNCATE", unix.NOTE_TRUNCATE},
|
||||||
|
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||||
|
}
|
||||||
45
vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
generated
vendored
Normal file
45
vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask int32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m int32
|
||||||
|
}{
|
||||||
|
{"FILE_ACCESS", unix.FILE_ACCESS},
|
||||||
|
{"FILE_MODIFIED", unix.FILE_MODIFIED},
|
||||||
|
{"FILE_ATTRIB", unix.FILE_ATTRIB},
|
||||||
|
{"FILE_TRUNC", unix.FILE_TRUNC},
|
||||||
|
{"FILE_NOFOLLOW", unix.FILE_NOFOLLOW},
|
||||||
|
{"FILE_DELETE", unix.FILE_DELETE},
|
||||||
|
{"FILE_RENAME_TO", unix.FILE_RENAME_TO},
|
||||||
|
{"FILE_RENAME_FROM", unix.FILE_RENAME_FROM},
|
||||||
|
{"UNMOUNTED", unix.UNMOUNTED},
|
||||||
|
{"MOUNTEDOVER", unix.MOUNTEDOVER},
|
||||||
|
{"FILE_EXCEPTION", unix.FILE_EXCEPTION},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-30s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
|
||||||
|
}
|
||||||
40
vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
generated
vendored
Normal file
40
vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Debug(name string, mask uint32) {
|
||||||
|
names := []struct {
|
||||||
|
n string
|
||||||
|
m uint32
|
||||||
|
}{
|
||||||
|
{"FILE_ACTION_ADDED", windows.FILE_ACTION_ADDED},
|
||||||
|
{"FILE_ACTION_REMOVED", windows.FILE_ACTION_REMOVED},
|
||||||
|
{"FILE_ACTION_MODIFIED", windows.FILE_ACTION_MODIFIED},
|
||||||
|
{"FILE_ACTION_RENAMED_OLD_NAME", windows.FILE_ACTION_RENAMED_OLD_NAME},
|
||||||
|
{"FILE_ACTION_RENAMED_NEW_NAME", windows.FILE_ACTION_RENAMED_NEW_NAME},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l []string
|
||||||
|
unknown = mask
|
||||||
|
)
|
||||||
|
for _, n := range names {
|
||||||
|
if mask&n.m == n.m {
|
||||||
|
l = append(l, n.n)
|
||||||
|
unknown ^= n.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if unknown > 0 {
|
||||||
|
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %-65s → %q\n",
|
||||||
|
time.Now().Format("15:04:05.000000000"), strings.Join(l, " | "), filepath.ToSlash(name))
|
||||||
|
}
|
||||||
31
vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
generated
vendored
Normal file
31
vendor/github.com/fsnotify/fsnotify/internal/freebsd.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = uint64(l.Cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) }
|
||||||
2
vendor/github.com/fsnotify/fsnotify/internal/internal.go
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/internal/internal.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Package internal contains some helpers.
|
||||||
|
package internal
|
||||||
31
vendor/github.com/fsnotify/fsnotify/internal/unix.go
generated
vendored
Normal file
31
vendor/github.com/fsnotify/fsnotify/internal/unix.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//go:build !windows && !darwin && !freebsd && !plan9
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = syscall.EACCES
|
||||||
|
ErrUnixEACCES = unix.EACCES
|
||||||
|
)
|
||||||
|
|
||||||
|
var maxfiles uint64
|
||||||
|
|
||||||
|
func SetRlimit() {
|
||||||
|
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||||
|
var l syscall.Rlimit
|
||||||
|
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
if err == nil && l.Cur != l.Max {
|
||||||
|
l.Cur = l.Max
|
||||||
|
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||||
|
}
|
||||||
|
maxfiles = uint64(l.Cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Maxfiles() uint64 { return maxfiles }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||||
7
vendor/github.com/fsnotify/fsnotify/internal/unix2.go
generated
vendored
Normal file
7
vendor/github.com/fsnotify/fsnotify/internal/unix2.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
func HasPrivilegesForSymlink() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
41
vendor/github.com/fsnotify/fsnotify/internal/windows.go
generated
vendored
Normal file
41
vendor/github.com/fsnotify/fsnotify/internal/windows.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Just a dummy.
|
||||||
|
var (
|
||||||
|
ErrSyscallEACCES = errors.New("dummy")
|
||||||
|
ErrUnixEACCES = errors.New("dummy")
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetRlimit() {}
|
||||||
|
func Maxfiles() uint64 { return 1<<64 - 1 }
|
||||||
|
func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") }
|
||||||
|
func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") }
|
||||||
|
|
||||||
|
func HasPrivilegesForSymlink() bool {
|
||||||
|
var sid *windows.SID
|
||||||
|
err := windows.AllocateAndInitializeSid(
|
||||||
|
&windows.SECURITY_NT_AUTHORITY,
|
||||||
|
2,
|
||||||
|
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||||
|
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
&sid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer windows.FreeSid(sid)
|
||||||
|
token := windows.Token(0)
|
||||||
|
member, err := token.IsMember(sid)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return member || token.IsElevated()
|
||||||
|
}
|
||||||
64
vendor/github.com/fsnotify/fsnotify/shared.go
generated
vendored
Normal file
64
vendor/github.com/fsnotify/fsnotify/shared.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type shared struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan struct{}
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShared(ev chan Event, errs chan error) *shared {
|
||||||
|
return &shared{
|
||||||
|
Events: ev,
|
||||||
|
Errors: errs,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the event was sent, or false if watcher is closed.
|
||||||
|
func (w *shared) sendEvent(e Event) bool {
|
||||||
|
if e.Op == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Events <- e:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the error was sent, or false if watcher is closed.
|
||||||
|
func (w *shared) sendError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return false
|
||||||
|
case w.Errors <- err:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *shared) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as closed; returns true if it was already closed.
|
||||||
|
func (w *shared) close() bool {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
if w.isClosed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
close(w.done)
|
||||||
|
return false
|
||||||
|
}
|
||||||
3
vendor/github.com/fsnotify/fsnotify/staticcheck.conf
generated
vendored
Normal file
3
vendor/github.com/fsnotify/fsnotify/staticcheck.conf
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
checks = ['all',
|
||||||
|
'-U1000', # Don't complain about unused functions.
|
||||||
|
]
|
||||||
7
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
7
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
||||||
8
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
8
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
||||||
26
vendor/github.com/go-openapi/jsonpointer/.editorconfig
generated
vendored
Normal file
26
vendor/github.com/go-openapi/jsonpointer/.editorconfig
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# Tab indentation (no size specified)
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
1
vendor/github.com/go-openapi/jsonpointer/.gitignore
generated
vendored
Normal file
1
vendor/github.com/go-openapi/jsonpointer/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
secrets.yml
|
||||||
15
vendor/github.com/go-openapi/jsonpointer/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/go-openapi/jsonpointer/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
go:
|
||||||
|
- 1.14.x
|
||||||
|
- 1.15.x
|
||||||
|
install:
|
||||||
|
- GO111MODULE=off go get -u gotest.tools/gotestsum
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
language: go
|
||||||
|
notifications:
|
||||||
|
slack:
|
||||||
|
secure: a5VgoiwB1G/AZqzmephPZIhEB9avMlsWSlVnM1dSAtYAwdrQHGTQxAmpOxYIoSPDhWNN5bfZmjd29++UlTwLcHSR+e0kJhH6IfDlsHj/HplNCJ9tyI0zYc7XchtdKgeMxMzBKCzgwFXGSbQGydXTliDNBo0HOzmY3cou/daMFTP60K+offcjS+3LRAYb1EroSRXZqrk1nuF/xDL3792DZUdPMiFR/L/Df6y74D6/QP4sTkTDFQitz4Wy/7jbsfj8dG6qK2zivgV6/l+w4OVjFkxVpPXogDWY10vVXNVynqxfJ7to2d1I9lNCHE2ilBCkWMIPdyJF7hjF8pKW+82yP4EzRh0vu8Xn0HT5MZpQxdRY/YMxNrWaG7SxsoEaO4q5uhgdzAqLYY3TRa7MjIK+7Ur+aqOeTXn6OKwVi0CjvZ6mIU3WUKSwiwkFZMbjRAkSb5CYwMEfGFO/z964xz83qGt6WAtBXNotqCQpTIiKtDHQeLOMfksHImCg6JLhQcWBVxamVgu0G3Pdh8Y6DyPnxraXY95+QDavbjqv7TeYT9T/FNnrkXaTTK0s4iWE5H4ACU0Qvz0wUYgfQrZv0/Hp7V17+rabUwnzYySHCy9SWX/7OV9Cfh31iMp9ZIffr76xmmThtOEqs8TrTtU6BWI3rWwvA9cXQipZTVtL0oswrGw=
|
||||||
|
script:
|
||||||
|
- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
74
vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md
generated
vendored
Normal file
74
vendor/github.com/go-openapi/jsonpointer/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
202
vendor/github.com/go-openapi/jsonpointer/LICENSE
generated
vendored
Normal file
202
vendor/github.com/go-openapi/jsonpointer/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
15
vendor/github.com/go-openapi/jsonpointer/README.md
generated
vendored
Normal file
15
vendor/github.com/go-openapi/jsonpointer/README.md
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# gojsonpointer [](https://travis-ci.org/go-openapi/jsonpointer) [](https://codecov.io/gh/go-openapi/jsonpointer) [](https://slackin.goswagger.io)
|
||||||
|
|
||||||
|
[](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE) [](http://godoc.org/github.com/go-openapi/jsonpointer)
|
||||||
|
An implementation of JSON Pointer - Go language
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Completed YES
|
||||||
|
|
||||||
|
Tested YES
|
||||||
|
|
||||||
|
## References
|
||||||
|
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
||||||
|
|
||||||
|
### Note
|
||||||
|
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented.
|
||||||
390
vendor/github.com/go-openapi/jsonpointer/pointer.go
generated
vendored
Normal file
390
vendor/github.com/go-openapi/jsonpointer/pointer.go
generated
vendored
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// author sigu-399
|
||||||
|
// author-github https://github.com/sigu-399
|
||||||
|
// author-mail sigu.399@gmail.com
|
||||||
|
//
|
||||||
|
// repository-name jsonpointer
|
||||||
|
// repository-desc An implementation of JSON Pointer - Go language
|
||||||
|
//
|
||||||
|
// description Main and unique file.
|
||||||
|
//
|
||||||
|
// created 25-02-2013
|
||||||
|
|
||||||
|
package jsonpointer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
emptyPointer = ``
|
||||||
|
pointerSeparator = `/`
|
||||||
|
|
||||||
|
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
|
||||||
|
var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()
|
||||||
|
|
||||||
|
// JSONPointable is an interface for structs to implement when they need to customize the
|
||||||
|
// json pointer process
|
||||||
|
type JSONPointable interface {
|
||||||
|
JSONLookup(string) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONSetable is an interface for structs to implement when they need to customize the
|
||||||
|
// json pointer process
|
||||||
|
type JSONSetable interface {
|
||||||
|
JSONSet(string, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new json pointer for the given string
|
||||||
|
func New(jsonPointerString string) (Pointer, error) {
|
||||||
|
|
||||||
|
var p Pointer
|
||||||
|
err := p.parse(jsonPointerString)
|
||||||
|
return p, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer the json pointer reprsentation
|
||||||
|
type Pointer struct {
|
||||||
|
referenceTokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Constructor", parses the given string JSON pointer
|
||||||
|
func (p *Pointer) parse(jsonPointerString string) error {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if jsonPointerString != emptyPointer {
|
||||||
|
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
|
||||||
|
err = errors.New(invalidStart)
|
||||||
|
} else {
|
||||||
|
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
|
||||||
|
for _, referenceToken := range referenceTokens[1:] {
|
||||||
|
p.referenceTokens = append(p.referenceTokens, referenceToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get uses the pointer to retrieve a value from a JSON document
|
||||||
|
func (p *Pointer) Get(document interface{}) (interface{}, reflect.Kind, error) {
|
||||||
|
return p.get(document, swag.DefaultJSONNameProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set uses the pointer to set a value from a JSON document
|
||||||
|
func (p *Pointer) Set(document interface{}, value interface{}) (interface{}, error) {
|
||||||
|
return document, p.set(document, value, swag.DefaultJSONNameProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForToken gets a value for a json pointer token 1 level deep
|
||||||
|
func GetForToken(document interface{}, decodedToken string) (interface{}, reflect.Kind, error) {
|
||||||
|
return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetForToken gets a value for a json pointer token 1 level deep
|
||||||
|
func SetForToken(document interface{}, decodedToken string, value interface{}) (interface{}, error) {
|
||||||
|
return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
|
||||||
|
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||||
|
kind := rValue.Kind()
|
||||||
|
|
||||||
|
if rValue.Type().Implements(jsonPointableType) {
|
||||||
|
r, err := node.(JSONPointable).JSONLookup(decodedToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, kind, err
|
||||||
|
}
|
||||||
|
return r, kind, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||||
|
if !ok {
|
||||||
|
return nil, kind, fmt.Errorf("object has no field %q", decodedToken)
|
||||||
|
}
|
||||||
|
fld := rValue.FieldByName(nm)
|
||||||
|
return fld.Interface(), kind, nil
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
kv := reflect.ValueOf(decodedToken)
|
||||||
|
mv := rValue.MapIndex(kv)
|
||||||
|
|
||||||
|
if mv.IsValid() {
|
||||||
|
return mv.Interface(), kind, nil
|
||||||
|
}
|
||||||
|
return nil, kind, fmt.Errorf("object has no key %q", decodedToken)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, kind, err
|
||||||
|
}
|
||||||
|
sLength := rValue.Len()
|
||||||
|
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||||
|
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength-1, tokenIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := rValue.Index(tokenIndex)
|
||||||
|
return elem.Interface(), kind, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *swag.NameProvider) error {
|
||||||
|
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||||
|
|
||||||
|
if ns, ok := node.(JSONSetable); ok { // pointer impl
|
||||||
|
return ns.JSONSet(decodedToken, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rValue.Type().Implements(jsonSetableType) {
|
||||||
|
return node.(JSONSetable).JSONSet(decodedToken, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rValue.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("object has no field %q", decodedToken)
|
||||||
|
}
|
||||||
|
fld := rValue.FieldByName(nm)
|
||||||
|
if fld.IsValid() {
|
||||||
|
fld.Set(reflect.ValueOf(data))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
kv := reflect.ValueOf(decodedToken)
|
||||||
|
rValue.SetMapIndex(kv, reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sLength := rValue.Len()
|
||||||
|
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||||
|
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := rValue.Index(tokenIndex)
|
||||||
|
if !elem.CanSet() {
|
||||||
|
return fmt.Errorf("can't set slice index %s to %v", decodedToken, data)
|
||||||
|
}
|
||||||
|
elem.Set(reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid token reference %q", decodedToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) {
|
||||||
|
|
||||||
|
if nameProvider == nil {
|
||||||
|
nameProvider = swag.DefaultJSONNameProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := reflect.Invalid
|
||||||
|
|
||||||
|
// Full document when empty
|
||||||
|
if len(p.referenceTokens) == 0 {
|
||||||
|
return node, kind, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, token := range p.referenceTokens {
|
||||||
|
|
||||||
|
decodedToken := Unescape(token)
|
||||||
|
|
||||||
|
r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, knd, err
|
||||||
|
}
|
||||||
|
node, kind = r, knd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rValue := reflect.ValueOf(node)
|
||||||
|
kind = rValue.Kind()
|
||||||
|
|
||||||
|
return node, kind, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pointer) set(node, data interface{}, nameProvider *swag.NameProvider) error {
|
||||||
|
knd := reflect.ValueOf(node).Kind()
|
||||||
|
|
||||||
|
if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
|
||||||
|
return fmt.Errorf("only structs, pointers, maps and slices are supported for setting values")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nameProvider == nil {
|
||||||
|
nameProvider = swag.DefaultJSONNameProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full document when empty
|
||||||
|
if len(p.referenceTokens) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastI := len(p.referenceTokens) - 1
|
||||||
|
for i, token := range p.referenceTokens {
|
||||||
|
isLastToken := i == lastI
|
||||||
|
decodedToken := Unescape(token)
|
||||||
|
|
||||||
|
if isLastToken {
|
||||||
|
|
||||||
|
return setSingleImpl(node, data, decodedToken, nameProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||||
|
kind := rValue.Kind()
|
||||||
|
|
||||||
|
if rValue.Type().Implements(jsonPointableType) {
|
||||||
|
r, err := node.(JSONPointable).JSONLookup(decodedToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fld := reflect.ValueOf(r)
|
||||||
|
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
|
||||||
|
node = fld.Addr().Interface()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node = r
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("object has no field %q", decodedToken)
|
||||||
|
}
|
||||||
|
fld := rValue.FieldByName(nm)
|
||||||
|
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
|
||||||
|
node = fld.Addr().Interface()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node = fld.Interface()
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
kv := reflect.ValueOf(decodedToken)
|
||||||
|
mv := rValue.MapIndex(kv)
|
||||||
|
|
||||||
|
if !mv.IsValid() {
|
||||||
|
return fmt.Errorf("object has no key %q", decodedToken)
|
||||||
|
}
|
||||||
|
if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr {
|
||||||
|
node = mv.Addr().Interface()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node = mv.Interface()
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sLength := rValue.Len()
|
||||||
|
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||||
|
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := rValue.Index(tokenIndex)
|
||||||
|
if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Ptr {
|
||||||
|
node = elem.Addr().Interface()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node = elem.Interface()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid token reference %q", decodedToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodedTokens returns the decoded tokens
|
||||||
|
func (p *Pointer) DecodedTokens() []string {
|
||||||
|
result := make([]string, 0, len(p.referenceTokens))
|
||||||
|
for _, t := range p.referenceTokens {
|
||||||
|
result = append(result, Unescape(t))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if this is an empty json pointer
|
||||||
|
// this indicates that it points to the root document
|
||||||
|
func (p *Pointer) IsEmpty() bool {
|
||||||
|
return len(p.referenceTokens) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer to string representation function
|
||||||
|
func (p *Pointer) String() string {
|
||||||
|
|
||||||
|
if len(p.referenceTokens) == 0 {
|
||||||
|
return emptyPointer
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
|
||||||
|
|
||||||
|
return pointerString
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specific JSON pointer encoding here
|
||||||
|
// ~0 => ~
|
||||||
|
// ~1 => /
|
||||||
|
// ... and vice versa
|
||||||
|
|
||||||
|
const (
|
||||||
|
encRefTok0 = `~0`
|
||||||
|
encRefTok1 = `~1`
|
||||||
|
decRefTok0 = `~`
|
||||||
|
decRefTok1 = `/`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unescape unescapes a json pointer reference token string to the original representation
|
||||||
|
func Unescape(token string) string {
|
||||||
|
step1 := strings.Replace(token, encRefTok1, decRefTok1, -1)
|
||||||
|
step2 := strings.Replace(step1, encRefTok0, decRefTok0, -1)
|
||||||
|
return step2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape escapes a pointer reference token string
|
||||||
|
func Escape(token string) string {
|
||||||
|
step1 := strings.Replace(token, decRefTok0, encRefTok0, -1)
|
||||||
|
step2 := strings.Replace(step1, decRefTok1, encRefTok1, -1)
|
||||||
|
return step2
|
||||||
|
}
|
||||||
1
vendor/github.com/go-openapi/jsonreference/.gitignore
generated
vendored
Normal file
1
vendor/github.com/go-openapi/jsonreference/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
secrets.yml
|
||||||
41
vendor/github.com/go-openapi/jsonreference/.golangci.yml
generated
vendored
Normal file
41
vendor/github.com/go-openapi/jsonreference/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 30
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 4
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- lll
|
||||||
|
- gochecknoglobals
|
||||||
|
- godox
|
||||||
|
- gocognit
|
||||||
|
- whitespace
|
||||||
|
- wsl
|
||||||
|
- funlen
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- scopelint
|
||||||
|
- wrapcheck
|
||||||
|
- exhaustivestruct
|
||||||
|
- exhaustive
|
||||||
|
- nlreturn
|
||||||
|
- testpackage
|
||||||
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
- goerr113
|
||||||
|
- gomnd
|
||||||
|
- tparallel
|
||||||
|
- nestif
|
||||||
|
- godot
|
||||||
|
- errorlint
|
||||||
24
vendor/github.com/go-openapi/jsonreference/.travis.yml
generated
vendored
Normal file
24
vendor/github.com/go-openapi/jsonreference/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
go:
|
||||||
|
- 1.14.x
|
||||||
|
- 1.x
|
||||||
|
install:
|
||||||
|
- go get gotest.tools/gotestsum
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
# include linting job, but only for latest go version and amd64 arch
|
||||||
|
- go: 1.x
|
||||||
|
arch: amd64
|
||||||
|
install:
|
||||||
|
go get github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
script:
|
||||||
|
- golangci-lint run --new-from-rev master
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
language: go
|
||||||
|
notifications:
|
||||||
|
slack:
|
||||||
|
secure: OpQG/36F7DSF00HLm9WZMhyqFCYYyYTsVDObW226cWiR8PWYiNfLZiSEvIzT1Gx4dDjhigKTIqcLhG34CkL5iNXDjm9Yyo2RYhQPlK8NErNqUEXuBqn4RqYHW48VGhEhOyDd4Ei0E2FN5ZbgpvHgtpkdZ6XDi64r3Ac89isP9aPHXQTuv2Jog6b4/OKKiUTftLcTIst0p4Cp3gqOJWf1wnoj+IadWiECNVQT6zb47IYjtyw6+uV8iUjTzdKcRB6Zc6b4Dq7JAg1Zd7Jfxkql3hlKp4PNlRf9Cy7y5iA3G7MLyg3FcPX5z2kmcyPt2jOTRMBWUJ5zIQpOxizAcN8WsT3WWBL5KbuYK6k0PzujrIDLqdxGpNmjkkMfDBT9cKmZpm2FdW+oZgPFJP+oKmAo4u4KJz/vjiPTXgQlN5bmrLuRMCp+AwC5wkIohTqWZVPE2TK6ZSnMYcg/W39s+RP/9mJoyryAvPSpBOLTI+biCgaUCTOAZxNTWpMFc3tPYntc41WWkdKcooZ9JA5DwfcaVFyTGQ3YXz+HvX6G1z/gW0Q/A4dBi9mj2iE1xm7tRTT+4VQ2AXFvSEI1HJpfPgYnwAtwOD1v3Qm2EUHk9sCdtEDR4wVGEPIVn44GnwFMnGKx9JWppMPYwFu3SVDdHt+E+LOlhZUply11Aa+IVrT2KUQ=
|
||||||
|
script:
|
||||||
|
- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
74
vendor/github.com/go-openapi/jsonreference/CODE_OF_CONDUCT.md
generated
vendored
Normal file
74
vendor/github.com/go-openapi/jsonreference/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
202
vendor/github.com/go-openapi/jsonreference/LICENSE
generated
vendored
Normal file
202
vendor/github.com/go-openapi/jsonreference/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
15
vendor/github.com/go-openapi/jsonreference/README.md
generated
vendored
Normal file
15
vendor/github.com/go-openapi/jsonreference/README.md
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# gojsonreference [](https://travis-ci.org/go-openapi/jsonreference) [](https://codecov.io/gh/go-openapi/jsonreference) [](https://slackin.goswagger.io)
|
||||||
|
|
||||||
|
[](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE) [](http://godoc.org/github.com/go-openapi/jsonreference)
|
||||||
|
An implementation of JSON Reference - Go language
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Feature complete. Stable API
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
https://github.com/go-openapi/jsonpointer
|
||||||
|
|
||||||
|
## References
|
||||||
|
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
|
||||||
|
|
||||||
|
http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03
|
||||||
63
vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go
generated
vendored
Normal file
63
vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHttpPort = ":80"
|
||||||
|
defaultHttpsPort = ":443"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regular expressions used by the normalizations
|
||||||
|
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
||||||
|
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
||||||
|
|
||||||
|
// NormalizeURL will normalize the specified URL
|
||||||
|
// This was added to replace a previous call to the no longer maintained purell library:
|
||||||
|
// The call that was used looked like the following:
|
||||||
|
// url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes))
|
||||||
|
//
|
||||||
|
// To explain all that was included in the call above, purell.FlagsSafe was really just the following:
|
||||||
|
// - FlagLowercaseScheme
|
||||||
|
// - FlagLowercaseHost
|
||||||
|
// - FlagRemoveDefaultPort
|
||||||
|
// - FlagRemoveDuplicateSlashes (and this was mixed in with the |)
|
||||||
|
func NormalizeURL(u *url.URL) {
|
||||||
|
lowercaseScheme(u)
|
||||||
|
lowercaseHost(u)
|
||||||
|
removeDefaultPort(u)
|
||||||
|
removeDuplicateSlashes(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercaseScheme(u *url.URL) {
|
||||||
|
if len(u.Scheme) > 0 {
|
||||||
|
u.Scheme = strings.ToLower(u.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowercaseHost(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
u.Host = strings.ToLower(u.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDefaultPort(u *url.URL) {
|
||||||
|
if len(u.Host) > 0 {
|
||||||
|
scheme := strings.ToLower(u.Scheme)
|
||||||
|
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
||||||
|
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDuplicateSlashes(u *url.URL) {
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
158
vendor/github.com/go-openapi/jsonreference/reference.go
generated
vendored
Normal file
158
vendor/github.com/go-openapi/jsonreference/reference.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// author sigu-399
|
||||||
|
// author-github https://github.com/sigu-399
|
||||||
|
// author-mail sigu.399@gmail.com
|
||||||
|
//
|
||||||
|
// repository-name jsonreference
|
||||||
|
// repository-desc An implementation of JSON Reference - Go language
|
||||||
|
//
|
||||||
|
// description Main and unique file.
|
||||||
|
//
|
||||||
|
// created 26-02-2013
|
||||||
|
|
||||||
|
package jsonreference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/jsonreference/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fragmentRune = `#`
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new reference for the given string
|
||||||
|
func New(jsonReferenceString string) (Ref, error) {
|
||||||
|
|
||||||
|
var r Ref
|
||||||
|
err := r.parse(jsonReferenceString)
|
||||||
|
return r, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCreateRef parses the ref string and panics when it's invalid.
|
||||||
|
// Use the New method for a version that returns an error
|
||||||
|
func MustCreateRef(ref string) Ref {
|
||||||
|
r, err := New(ref)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref represents a json reference object
|
||||||
|
type Ref struct {
|
||||||
|
referenceURL *url.URL
|
||||||
|
referencePointer jsonpointer.Pointer
|
||||||
|
|
||||||
|
HasFullURL bool
|
||||||
|
HasURLPathOnly bool
|
||||||
|
HasFragmentOnly bool
|
||||||
|
HasFileScheme bool
|
||||||
|
HasFullFilePath bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURL gets the URL for this reference
|
||||||
|
func (r *Ref) GetURL() *url.URL {
|
||||||
|
return r.referenceURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPointer gets the json pointer for this reference
|
||||||
|
func (r *Ref) GetPointer() *jsonpointer.Pointer {
|
||||||
|
return &r.referencePointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the best version of the url for this reference
|
||||||
|
func (r *Ref) String() string {
|
||||||
|
|
||||||
|
if r.referenceURL != nil {
|
||||||
|
return r.referenceURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.HasFragmentOnly {
|
||||||
|
return fragmentRune + r.referencePointer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.referencePointer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRoot returns true if this reference is a root document
|
||||||
|
func (r *Ref) IsRoot() bool {
|
||||||
|
return r.referenceURL != nil &&
|
||||||
|
!r.IsCanonical() &&
|
||||||
|
!r.HasURLPathOnly &&
|
||||||
|
r.referenceURL.Fragment == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCanonical returns true when this pointer starts with http(s):// or file://
|
||||||
|
func (r *Ref) IsCanonical() bool {
|
||||||
|
return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Constructor", parses the given string JSON reference
|
||||||
|
func (r *Ref) parse(jsonReferenceString string) error {
|
||||||
|
|
||||||
|
parsed, err := url.Parse(jsonReferenceString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.NormalizeURL(parsed)
|
||||||
|
|
||||||
|
r.referenceURL = parsed
|
||||||
|
refURL := r.referenceURL
|
||||||
|
|
||||||
|
if refURL.Scheme != "" && refURL.Host != "" {
|
||||||
|
r.HasFullURL = true
|
||||||
|
} else {
|
||||||
|
if refURL.Path != "" {
|
||||||
|
r.HasURLPathOnly = true
|
||||||
|
} else if refURL.RawQuery == "" && refURL.Fragment != "" {
|
||||||
|
r.HasFragmentOnly = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.HasFileScheme = refURL.Scheme == "file"
|
||||||
|
r.HasFullFilePath = strings.HasPrefix(refURL.Path, "/")
|
||||||
|
|
||||||
|
// invalid json-pointer error means url has no json-pointer fragment. simply ignore error
|
||||||
|
r.referencePointer, _ = jsonpointer.New(refURL.Fragment)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherits creates a new reference from a parent and a child
|
||||||
|
// If the child cannot inherit from the parent, an error is returned
|
||||||
|
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
||||||
|
childURL := child.GetURL()
|
||||||
|
parentURL := r.GetURL()
|
||||||
|
if childURL == nil {
|
||||||
|
return nil, errors.New("child url is nil")
|
||||||
|
}
|
||||||
|
if parentURL == nil {
|
||||||
|
return &child, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := New(parentURL.ResolveReference(childURL).String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ref, nil
|
||||||
|
}
|
||||||
26
vendor/github.com/go-openapi/spec/.editorconfig
generated
vendored
Normal file
26
vendor/github.com/go-openapi/spec/.editorconfig
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# Tab indentation (no size specified)
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# Matches the exact files either package.json or .travis.yml
|
||||||
|
[{package.json,.travis.yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
2
vendor/github.com/go-openapi/spec/.gitignore
generated
vendored
Normal file
2
vendor/github.com/go-openapi/spec/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
secrets.yml
|
||||||
|
coverage.out
|
||||||
42
vendor/github.com/go-openapi/spec/.golangci.yml
generated
vendored
Normal file
42
vendor/github.com/go-openapi/spec/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 45
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
dupl:
|
||||||
|
threshold: 200
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- unparam
|
||||||
|
- lll
|
||||||
|
- gochecknoinits
|
||||||
|
- gochecknoglobals
|
||||||
|
- funlen
|
||||||
|
- godox
|
||||||
|
- gocognit
|
||||||
|
- whitespace
|
||||||
|
- wsl
|
||||||
|
- wrapcheck
|
||||||
|
- testpackage
|
||||||
|
- nlreturn
|
||||||
|
- gomnd
|
||||||
|
- exhaustivestruct
|
||||||
|
- goerr113
|
||||||
|
- errorlint
|
||||||
|
- nestif
|
||||||
|
- godot
|
||||||
|
- gofumpt
|
||||||
|
- paralleltest
|
||||||
|
- tparallel
|
||||||
|
- thelper
|
||||||
|
- ifshort
|
||||||
31
vendor/github.com/go-openapi/spec/.travis.yml
generated
vendored
Normal file
31
vendor/github.com/go-openapi/spec/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
go:
|
||||||
|
- 1.16.x
|
||||||
|
- 1.x
|
||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
# only run fast tests on ppc64le
|
||||||
|
- go: 1.x
|
||||||
|
arch: ppc64le
|
||||||
|
script:
|
||||||
|
- gotestsum -f short-verbose -- ./...
|
||||||
|
|
||||||
|
# include linting job, but only for latest go version and amd64 arch
|
||||||
|
- go: 1.x
|
||||||
|
arch: amd64
|
||||||
|
install:
|
||||||
|
go get github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
script:
|
||||||
|
- golangci-lint run --new-from-rev master
|
||||||
|
|
||||||
|
install:
|
||||||
|
- GO111MODULE=off go get -u gotest.tools/gotestsum
|
||||||
|
language: go
|
||||||
|
notifications:
|
||||||
|
slack:
|
||||||
|
secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E=
|
||||||
|
script:
|
||||||
|
- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
74
vendor/github.com/go-openapi/spec/CODE_OF_CONDUCT.md
generated
vendored
Normal file
74
vendor/github.com/go-openapi/spec/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
202
vendor/github.com/go-openapi/spec/LICENSE
generated
vendored
Normal file
202
vendor/github.com/go-openapi/spec/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
34
vendor/github.com/go-openapi/spec/README.md
generated
vendored
Normal file
34
vendor/github.com/go-openapi/spec/README.md
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# OAI object model
|
||||||
|
|
||||||
|
[](https://travis-ci.org/go-openapi/spec)
|
||||||
|
<!-- [](https://ci.appveyor.com/project/casualjim/go-openapi/spec/branch/master) -->
|
||||||
|
[](https://codecov.io/gh/go-openapi/spec)
|
||||||
|
[](https://slackin.goswagger.io)
|
||||||
|
[](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE)
|
||||||
|
[](https://pkg.go.dev/github.com/go-openapi/spec)
|
||||||
|
[](https://goreportcard.com/report/github.com/go-openapi/spec)
|
||||||
|
|
||||||
|
The object model for OpenAPI specification documents.
|
||||||
|
|
||||||
|
### FAQ
|
||||||
|
|
||||||
|
* What does this do?
|
||||||
|
|
||||||
|
> 1. This package knows how to marshal and unmarshal Swagger API specifications into a golang object model
|
||||||
|
> 2. It knows how to resolve $ref and expand them to make a single root document
|
||||||
|
|
||||||
|
* How does it play with the rest of the go-openapi packages ?
|
||||||
|
|
||||||
|
> 1. This package is at the core of the go-openapi suite of packages and [code generator](https://github.com/go-swagger/go-swagger)
|
||||||
|
> 2. There is a [spec loading package](https://github.com/go-openapi/loads) to fetch specs as JSON or YAML from local or remote locations
|
||||||
|
> 3. There is a [spec validation package](https://github.com/go-openapi/validate) built on top of it
|
||||||
|
> 4. There is a [spec analysis package](https://github.com/go-openapi/analysis) built on top of it, to analyze, flatten, fix and merge spec documents
|
||||||
|
|
||||||
|
* Does this library support OpenAPI 3?
|
||||||
|
|
||||||
|
> No.
|
||||||
|
> This package currently only supports OpenAPI 2.0 (aka Swagger 2.0).
|
||||||
|
> There is no plan to make it evolve toward supporting OpenAPI 3.x.
|
||||||
|
> This [discussion thread](https://github.com/go-openapi/spec/issues/21) relates the full story.
|
||||||
|
>
|
||||||
|
> An early attempt to support Swagger 3 may be found at: https://github.com/go-openapi/spec3
|
||||||
32
vendor/github.com/go-openapi/spec/appveyor.yml
generated
vendored
Normal file
32
vendor/github.com/go-openapi/spec/appveyor.yml
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
version: "0.1.{build}"
|
||||||
|
|
||||||
|
clone_folder: C:\go-openapi\spec
|
||||||
|
shallow_clone: true # for startup speed
|
||||||
|
pull_requests:
|
||||||
|
do_not_increment_build_number: true
|
||||||
|
|
||||||
|
#skip_tags: true
|
||||||
|
#skip_branch_with_pr: true
|
||||||
|
|
||||||
|
# appveyor.yml
|
||||||
|
build: off
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
|
||||||
|
stack: go 1.15
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- go test -v -timeout 20m ./...
|
||||||
|
|
||||||
|
deploy: off
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
- provider: Slack
|
||||||
|
incoming_webhook: https://hooks.slack.com/services/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
|
||||||
|
auth_token:
|
||||||
|
secure: Sf7kZf7ZGbnwWUMpffHwMu5A0cHkLK2MYY32LNTPj4+/3qC3Ghl7+9v4TSLOqOlCwdRNjOGblAq7s+GDJed6/xgRQl1JtCi1klzZNrYX4q01pgTPvvGcwbBkIYgeMaPeIRcK9OZnud7sRXdttozgTOpytps2U6Js32ip7uj5mHSg2ub0FwoSJwlS6dbezZ8+eDhoha0F/guY99BEwx8Bd+zROrT2TFGsSGOFGN6wFc7moCqTHO/YkWib13a2QNXqOxCCVBy/lt76Wp+JkeFppjHlzs/2lP3EAk13RIUAaesdEUHvIHrzCyNJEd3/+KO2DzsWOYfpktd+KBCvgaYOsoo7ubdT3IROeAegZdCgo/6xgCEsmFc9ZcqCfN5yNx2A+BZ2Vwmpws+bQ1E1+B5HDzzaiLcYfG4X2O210QVGVDLWsv1jqD+uPYeHY2WRfh5ZsIUFvaqgUEnwHwrK44/8REAhQavt1QAj5uJpsRd7CkRVPWRNK+yIky+wgbVUFEchRNmS55E7QWf+W4+4QZkQi7vUTMc9nbTUu2Es9NfvfudOpM2wZbn98fjpb/qq/nRv6Bk+ca+7XD5/IgNLMbWp2ouDdzbiHLCOfDUiHiDJhLfFZx9Bwo7ZwfzeOlbrQX66bx7xRKYmOe4DLrXhNcpbsMa8qbfxlZRCmYbubB/Y8h4=
|
||||||
|
channel: bots
|
||||||
|
on_build_success: false
|
||||||
|
on_build_failure: true
|
||||||
|
on_build_status_changed: true
|
||||||
297
vendor/github.com/go-openapi/spec/bindata.go
generated
vendored
Normal file
297
vendor/github.com/go-openapi/spec/bindata.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
98
vendor/github.com/go-openapi/spec/cache.go
generated
vendored
Normal file
98
vendor/github.com/go-openapi/spec/cache.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolutionCache a cache for resolving urls
|
||||||
|
type ResolutionCache interface {
|
||||||
|
Get(string) (interface{}, bool)
|
||||||
|
Set(string, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleCache struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
store map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simpleCache) ShallowClone() ResolutionCache {
|
||||||
|
store := make(map[string]interface{}, len(s.store))
|
||||||
|
s.lock.RLock()
|
||||||
|
for k, v := range s.store {
|
||||||
|
store[k] = v
|
||||||
|
}
|
||||||
|
s.lock.RUnlock()
|
||||||
|
|
||||||
|
return &simpleCache{
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a cached URI
|
||||||
|
func (s *simpleCache) Get(uri string) (interface{}, bool) {
|
||||||
|
s.lock.RLock()
|
||||||
|
v, ok := s.store[uri]
|
||||||
|
|
||||||
|
s.lock.RUnlock()
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set caches a URI
|
||||||
|
func (s *simpleCache) Set(uri string, data interface{}) {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.store[uri] = data
|
||||||
|
s.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// resCache is a package level cache for $ref resolution and expansion.
|
||||||
|
// It is initialized lazily by methods that have the need for it: no
|
||||||
|
// memory is allocated unless some expander methods are called.
|
||||||
|
//
|
||||||
|
// It is initialized with JSON schema and swagger schema,
|
||||||
|
// which do not mutate during normal operations.
|
||||||
|
//
|
||||||
|
// All subsequent utilizations of this cache are produced from a shallow
|
||||||
|
// clone of this initial version.
|
||||||
|
resCache *simpleCache
|
||||||
|
onceCache sync.Once
|
||||||
|
|
||||||
|
_ ResolutionCache = &simpleCache{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// initResolutionCache initializes the URI resolution cache. To be wrapped in a sync.Once.Do call.
|
||||||
|
func initResolutionCache() {
|
||||||
|
resCache = defaultResolutionCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultResolutionCache() *simpleCache {
|
||||||
|
return &simpleCache{store: map[string]interface{}{
|
||||||
|
"http://swagger.io/v2/schema.json": MustLoadSwagger20Schema(),
|
||||||
|
"http://json-schema.org/draft-04/schema": MustLoadJSONSchemaDraft04(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheOrDefault(cache ResolutionCache) ResolutionCache {
|
||||||
|
onceCache.Do(initResolutionCache)
|
||||||
|
|
||||||
|
if cache != nil {
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a shallow clone of the base cache with swagger and json schema
|
||||||
|
return resCache.ShallowClone()
|
||||||
|
}
|
||||||
57
vendor/github.com/go-openapi/spec/contact_info.go
generated
vendored
Normal file
57
vendor/github.com/go-openapi/spec/contact_info.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContactInfo contact information for the exposed API.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#contactObject
|
||||||
|
type ContactInfo struct {
|
||||||
|
ContactInfoProps
|
||||||
|
VendorExtensible
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContactInfoProps hold the properties of a ContactInfo object
|
||||||
|
type ContactInfoProps struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates ContactInfo from json
|
||||||
|
func (c *ContactInfo) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &c.ContactInfoProps); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &c.VendorExtensible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON produces ContactInfo as json
|
||||||
|
func (c ContactInfo) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(c.ContactInfoProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(c.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b1, b2), nil
|
||||||
|
}
|
||||||
49
vendor/github.com/go-openapi/spec/debug.go
generated
vendored
Normal file
49
vendor/github.com/go-openapi/spec/debug.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Debug is true when the SWAGGER_DEBUG env var is not empty.
|
||||||
|
//
|
||||||
|
// It enables a more verbose logging of this package.
|
||||||
|
var Debug = os.Getenv("SWAGGER_DEBUG") != ""
|
||||||
|
|
||||||
|
var (
|
||||||
|
// specLogger is a debug logger for this package
|
||||||
|
specLogger *log.Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
debugOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugOptions() {
|
||||||
|
specLogger = log.New(os.Stdout, "spec:", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugLog(msg string, args ...interface{}) {
|
||||||
|
// A private, trivial trace logger, based on go-openapi/spec/expander.go:debugLog()
|
||||||
|
if Debug {
|
||||||
|
_, file1, pos1, _ := runtime.Caller(1)
|
||||||
|
specLogger.Printf("%s:%d: %s", path.Base(file1), pos1, fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
19
vendor/github.com/go-openapi/spec/errors.go
generated
vendored
Normal file
19
vendor/github.com/go-openapi/spec/errors.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Error codes
|
||||||
|
var (
|
||||||
|
// ErrUnknownTypeForReference indicates that a resolved reference was found in an unsupported container type
|
||||||
|
ErrUnknownTypeForReference = errors.New("unknown type for the resolved reference")
|
||||||
|
|
||||||
|
// ErrResolveRefNeedsAPointer indicates that a $ref target must be a valid JSON pointer
|
||||||
|
ErrResolveRefNeedsAPointer = errors.New("resolve ref: target needs to be a pointer")
|
||||||
|
|
||||||
|
// ErrDerefUnsupportedType indicates that a resolved reference was found in an unsupported container type.
|
||||||
|
// At the moment, $ref are supported only inside: schemas, parameters, responses, path items
|
||||||
|
ErrDerefUnsupportedType = errors.New("deref: unsupported type")
|
||||||
|
|
||||||
|
// ErrExpandUnsupportedType indicates that $ref expansion is attempted on some invalid type
|
||||||
|
ErrExpandUnsupportedType = errors.New("expand: unsupported type. Input should be of type *Parameter or *Response")
|
||||||
|
)
|
||||||
594
vendor/github.com/go-openapi/spec/expander.go
generated
vendored
Normal file
594
vendor/github.com/go-openapi/spec/expander.go
generated
vendored
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExpandOptions provides options for the spec expander.
|
||||||
|
//
|
||||||
|
// RelativeBase is the path to the root document. This can be a remote URL or a path to a local file.
|
||||||
|
//
|
||||||
|
// If left empty, the root document is assumed to be located in the current working directory:
|
||||||
|
// all relative $ref's will be resolved from there.
|
||||||
|
//
|
||||||
|
// PathLoader injects a document loading method. By default, this resolves to the function provided by the SpecLoader package variable.
|
||||||
|
//
|
||||||
|
type ExpandOptions struct {
|
||||||
|
RelativeBase string // the path to the root document to expand. This is a file, not a directory
|
||||||
|
SkipSchemas bool // do not expand schemas, just paths, parameters and responses
|
||||||
|
ContinueOnError bool // continue expanding even after and error is found
|
||||||
|
PathLoader func(string) (json.RawMessage, error) `json:"-"` // the document loading method that takes a path as input and yields a json document
|
||||||
|
AbsoluteCircularRef bool // circular $ref remaining after expansion remain absolute URLs
|
||||||
|
}
|
||||||
|
|
||||||
|
func optionsOrDefault(opts *ExpandOptions) *ExpandOptions {
|
||||||
|
if opts != nil {
|
||||||
|
clone := *opts // shallow clone to avoid internal changes to be propagated to the caller
|
||||||
|
if clone.RelativeBase != "" {
|
||||||
|
clone.RelativeBase = normalizeBase(clone.RelativeBase)
|
||||||
|
}
|
||||||
|
// if the relative base is empty, let the schema loader choose a pseudo root document
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
return &ExpandOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandSpec expands the references in a swagger spec
|
||||||
|
func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||||
|
options = optionsOrDefault(options)
|
||||||
|
resolver := defaultSchemaLoader(spec, options, nil, nil)
|
||||||
|
|
||||||
|
specBasePath := options.RelativeBase
|
||||||
|
|
||||||
|
if !options.SkipSchemas {
|
||||||
|
for key, definition := range spec.Definitions {
|
||||||
|
parentRefs := make([]string, 0, 10)
|
||||||
|
parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key))
|
||||||
|
|
||||||
|
def, err := expandSchema(definition, parentRefs, resolver, specBasePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if def != nil {
|
||||||
|
spec.Definitions[key] = *def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range spec.Parameters {
|
||||||
|
parameter := spec.Parameters[key]
|
||||||
|
if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spec.Parameters[key] = parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range spec.Responses {
|
||||||
|
response := spec.Responses[key]
|
||||||
|
if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spec.Responses[key] = response
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.Paths != nil {
|
||||||
|
for key := range spec.Paths.Paths {
|
||||||
|
pth := spec.Paths.Paths[key]
|
||||||
|
if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spec.Paths.Paths[key] = pth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootBase = ".root"
|
||||||
|
|
||||||
|
// baseForRoot loads in the cache the root document and produces a fake ".root" base path entry
|
||||||
|
// for further $ref resolution
|
||||||
|
//
|
||||||
|
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||||
|
func baseForRoot(root interface{}, cache ResolutionCache) string {
|
||||||
|
if root == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache the root document to resolve $ref's
|
||||||
|
normalizedBase := normalizeBase(rootBase)
|
||||||
|
cache.Set(normalizedBase, root)
|
||||||
|
|
||||||
|
return normalizedBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandSchema expands the refs in the schema object with reference to the root object.
|
||||||
|
//
|
||||||
|
// go-openapi/validate uses this function.
|
||||||
|
//
|
||||||
|
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||||
|
// (use ExpandSchemaWithBasePath to resolve external references).
|
||||||
|
//
|
||||||
|
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||||
|
func ExpandSchema(schema *Schema, root interface{}, cache ResolutionCache) error {
|
||||||
|
cache = cacheOrDefault(cache)
|
||||||
|
if root == nil {
|
||||||
|
root = schema
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &ExpandOptions{
|
||||||
|
// when a root is specified, cache the root as an in-memory document for $ref retrieval
|
||||||
|
RelativeBase: baseForRoot(root, cache),
|
||||||
|
SkipSchemas: false,
|
||||||
|
ContinueOnError: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpandSchemaWithBasePath(schema, cache, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandSchemaWithBasePath expands the refs in the schema object, base path configured through expand options.
|
||||||
|
//
|
||||||
|
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||||
|
func ExpandSchemaWithBasePath(schema *Schema, cache ResolutionCache, opts *ExpandOptions) error {
|
||||||
|
if schema == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cache = cacheOrDefault(cache)
|
||||||
|
|
||||||
|
opts = optionsOrDefault(opts)
|
||||||
|
|
||||||
|
resolver := defaultSchemaLoader(nil, opts, cache, nil)
|
||||||
|
|
||||||
|
parentRefs := make([]string, 0, 10)
|
||||||
|
s, err := expandSchema(*schema, parentRefs, resolver, opts.RelativeBase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s != nil {
|
||||||
|
// guard for when continuing on error
|
||||||
|
*schema = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||||
|
if target.Items == nil {
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// array
|
||||||
|
if target.Items.Schema != nil {
|
||||||
|
t, err := expandSchema(*target.Items.Schema, parentRefs, resolver, basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*target.Items.Schema = *t
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuple
|
||||||
|
for i := range target.Items.Schemas {
|
||||||
|
t, err := expandSchema(target.Items.Schemas[i], parentRefs, resolver, basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
target.Items.Schemas[i] = *t
|
||||||
|
}
|
||||||
|
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||||
|
if target.Ref.String() == "" && target.Ref.IsRoot() {
|
||||||
|
newRef := normalizeRef(&target.Ref, basePath)
|
||||||
|
target.Ref = *newRef
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// change the base path of resolution when an ID is encountered
|
||||||
|
// otherwise the basePath should inherit the parent's
|
||||||
|
if target.ID != "" {
|
||||||
|
basePath, _ = resolver.setSchemaID(target, target.ID, basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Ref.String() != "" {
|
||||||
|
return expandSchemaRef(target, parentRefs, resolver, basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range target.Definitions {
|
||||||
|
tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if tt != nil {
|
||||||
|
target.Definitions[k] = *tt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := expandItems(target, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target = *t
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range target.AllOf {
|
||||||
|
t, err := expandSchema(target.AllOf[i], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target.AllOf[i] = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range target.AnyOf {
|
||||||
|
t, err := expandSchema(target.AnyOf[i], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target.AnyOf[i] = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range target.OneOf {
|
||||||
|
t, err := expandSchema(target.OneOf[i], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target.OneOf[i] = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Not != nil {
|
||||||
|
t, err := expandSchema(*target.Not, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
*target.Not = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range target.Properties {
|
||||||
|
t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target.Properties[k] = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.AdditionalProperties != nil && target.AdditionalProperties.Schema != nil {
|
||||||
|
t, err := expandSchema(*target.AdditionalProperties.Schema, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
*target.AdditionalProperties.Schema = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range target.PatternProperties {
|
||||||
|
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
target.PatternProperties[k] = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range target.Dependencies {
|
||||||
|
if target.Dependencies[k].Schema != nil {
|
||||||
|
t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
*target.Dependencies[k].Schema = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.AdditionalItems != nil && target.AdditionalItems.Schema != nil {
|
||||||
|
t, err := expandSchema(*target.AdditionalItems.Schema, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return &target, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
*target.AdditionalItems.Schema = *t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||||
|
// if a Ref is found, all sibling fields are skipped
|
||||||
|
// Ref also changes the resolution scope of children expandSchema
|
||||||
|
|
||||||
|
// here the resolution scope is changed because a $ref was encountered
|
||||||
|
normalizedRef := normalizeRef(&target.Ref, basePath)
|
||||||
|
normalizedBasePath := normalizedRef.RemoteURI()
|
||||||
|
|
||||||
|
if resolver.isCircular(normalizedRef, basePath, parentRefs...) {
|
||||||
|
// this means there is a cycle in the recursion tree: return the Ref
|
||||||
|
// - circular refs cannot be expanded. We leave them as ref.
|
||||||
|
// - denormalization means that a new local file ref is set relative to the original basePath
|
||||||
|
debugLog("short circuit circular ref: basePath: %s, normalizedPath: %s, normalized ref: %s",
|
||||||
|
basePath, normalizedBasePath, normalizedRef.String())
|
||||||
|
if !resolver.options.AbsoluteCircularRef {
|
||||||
|
target.Ref = denormalizeRef(normalizedRef, resolver.context.basePath, resolver.context.rootID)
|
||||||
|
} else {
|
||||||
|
target.Ref = *normalizedRef
|
||||||
|
}
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var t *Schema
|
||||||
|
err := resolver.Resolve(&target.Ref, &t, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t == nil {
|
||||||
|
// guard for when continuing on error
|
||||||
|
return &target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentRefs = append(parentRefs, normalizedRef.String())
|
||||||
|
transitiveResolver := resolver.transitiveResolver(basePath, target.Ref)
|
||||||
|
|
||||||
|
basePath = resolver.updateBasePath(transitiveResolver, normalizedBasePath)
|
||||||
|
|
||||||
|
return expandSchema(*t, parentRefs, transitiveResolver, basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string) error {
|
||||||
|
if pathItem == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentRefs := make([]string, 0, 10)
|
||||||
|
if err := resolver.deref(pathItem, parentRefs, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pathItem.Ref.String() != "" {
|
||||||
|
transitiveResolver := resolver.transitiveResolver(basePath, pathItem.Ref)
|
||||||
|
basePath = transitiveResolver.updateBasePath(resolver, basePath)
|
||||||
|
resolver = transitiveResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
pathItem.Ref = Ref{}
|
||||||
|
for i := range pathItem.Parameters {
|
||||||
|
if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ops := []*Operation{
|
||||||
|
pathItem.Get,
|
||||||
|
pathItem.Head,
|
||||||
|
pathItem.Options,
|
||||||
|
pathItem.Put,
|
||||||
|
pathItem.Post,
|
||||||
|
pathItem.Patch,
|
||||||
|
pathItem.Delete,
|
||||||
|
}
|
||||||
|
for _, op := range ops {
|
||||||
|
if err := expandOperation(op, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandOperation(op *Operation, resolver *schemaLoader, basePath string) error {
|
||||||
|
if op == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range op.Parameters {
|
||||||
|
param := op.Parameters[i]
|
||||||
|
if err := expandParameterOrResponse(¶m, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.Parameters[i] = param
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Responses == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := op.Responses
|
||||||
|
if err := expandParameterOrResponse(responses.Default, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for code := range responses.StatusCodeResponses {
|
||||||
|
response := responses.StatusCodeResponses[code]
|
||||||
|
if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
responses.StatusCodeResponses[code] = response
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandResponseWithRoot expands a response based on a root document, not a fetchable document
|
||||||
|
//
|
||||||
|
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||||
|
// (use ExpandResponse to resolve external references).
|
||||||
|
//
|
||||||
|
// Setting the cache is optional and this parameter may safely be left to nil.
|
||||||
|
func ExpandResponseWithRoot(response *Response, root interface{}, cache ResolutionCache) error {
|
||||||
|
cache = cacheOrDefault(cache)
|
||||||
|
opts := &ExpandOptions{
|
||||||
|
RelativeBase: baseForRoot(root, cache),
|
||||||
|
}
|
||||||
|
resolver := defaultSchemaLoader(root, opts, cache, nil)
|
||||||
|
|
||||||
|
return expandParameterOrResponse(response, resolver, opts.RelativeBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandResponse expands a response based on a basepath
|
||||||
|
//
|
||||||
|
// All refs inside response will be resolved relative to basePath
|
||||||
|
func ExpandResponse(response *Response, basePath string) error {
|
||||||
|
opts := optionsOrDefault(&ExpandOptions{
|
||||||
|
RelativeBase: basePath,
|
||||||
|
})
|
||||||
|
resolver := defaultSchemaLoader(nil, opts, nil, nil)
|
||||||
|
|
||||||
|
return expandParameterOrResponse(response, resolver, opts.RelativeBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandParameterWithRoot expands a parameter based on a root document, not a fetchable document.
|
||||||
|
//
|
||||||
|
// Notice that it is impossible to reference a json schema in a different document other than root
|
||||||
|
// (use ExpandParameter to resolve external references).
|
||||||
|
func ExpandParameterWithRoot(parameter *Parameter, root interface{}, cache ResolutionCache) error {
|
||||||
|
cache = cacheOrDefault(cache)
|
||||||
|
|
||||||
|
opts := &ExpandOptions{
|
||||||
|
RelativeBase: baseForRoot(root, cache),
|
||||||
|
}
|
||||||
|
resolver := defaultSchemaLoader(root, opts, cache, nil)
|
||||||
|
|
||||||
|
return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandParameter expands a parameter based on a basepath.
|
||||||
|
// This is the exported version of expandParameter
|
||||||
|
// all refs inside parameter will be resolved relative to basePath
|
||||||
|
func ExpandParameter(parameter *Parameter, basePath string) error {
|
||||||
|
opts := optionsOrDefault(&ExpandOptions{
|
||||||
|
RelativeBase: basePath,
|
||||||
|
})
|
||||||
|
resolver := defaultSchemaLoader(nil, opts, nil, nil)
|
||||||
|
|
||||||
|
return expandParameterOrResponse(parameter, resolver, opts.RelativeBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRefAndSchema(input interface{}) (*Ref, *Schema, error) {
|
||||||
|
var (
|
||||||
|
ref *Ref
|
||||||
|
sch *Schema
|
||||||
|
)
|
||||||
|
|
||||||
|
switch refable := input.(type) {
|
||||||
|
case *Parameter:
|
||||||
|
if refable == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
ref = &refable.Ref
|
||||||
|
sch = refable.Schema
|
||||||
|
case *Response:
|
||||||
|
if refable == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
ref = &refable.Ref
|
||||||
|
sch = refable.Schema
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("unsupported type: %T: %w", input, ErrExpandUnsupportedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref, sch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error {
|
||||||
|
ref, _, err := getRefAndSchema(input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentRefs := make([]string, 0, 10)
|
||||||
|
if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, sch, _ := getRefAndSchema(input)
|
||||||
|
if ref.String() != "" {
|
||||||
|
transitiveResolver := resolver.transitiveResolver(basePath, *ref)
|
||||||
|
basePath = resolver.updateBasePath(transitiveResolver, basePath)
|
||||||
|
resolver = transitiveResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
if sch == nil {
|
||||||
|
// nothing to be expanded
|
||||||
|
if ref != nil {
|
||||||
|
*ref = Ref{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sch.Ref.String() != "" {
|
||||||
|
rebasedRef, ern := NewRef(normalizeURI(sch.Ref.String(), basePath))
|
||||||
|
if ern != nil {
|
||||||
|
return ern
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case resolver.isCircular(&rebasedRef, basePath, parentRefs...):
|
||||||
|
// this is a circular $ref: stop expansion
|
||||||
|
if !resolver.options.AbsoluteCircularRef {
|
||||||
|
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
|
||||||
|
} else {
|
||||||
|
sch.Ref = rebasedRef
|
||||||
|
}
|
||||||
|
case !resolver.options.SkipSchemas:
|
||||||
|
// schema expanded to a $ref in another root
|
||||||
|
sch.Ref = rebasedRef
|
||||||
|
debugLog("rebased to: %s", sch.Ref.String())
|
||||||
|
default:
|
||||||
|
// skip schema expansion but rebase $ref to schema
|
||||||
|
sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref != nil {
|
||||||
|
*ref = Ref{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand schema
|
||||||
|
if !resolver.options.SkipSchemas {
|
||||||
|
s, err := expandSchema(*sch, parentRefs, resolver, basePath)
|
||||||
|
if resolver.shouldStopOnError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
// guard for when continuing on error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*sch = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
24
vendor/github.com/go-openapi/spec/external_docs.go
generated
vendored
Normal file
24
vendor/github.com/go-openapi/spec/external_docs.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
// ExternalDocumentation allows referencing an external resource for
|
||||||
|
// extended documentation.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#externalDocumentationObject
|
||||||
|
type ExternalDocumentation struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
203
vendor/github.com/go-openapi/spec/header.go
generated
vendored
Normal file
203
vendor/github.com/go-openapi/spec/header.go
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsonArray = "array"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderProps describes a response header
|
||||||
|
type HeaderProps struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header describes a header for a response of the API
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#headerObject
|
||||||
|
type Header struct {
|
||||||
|
CommonValidations
|
||||||
|
SimpleSchema
|
||||||
|
VendorExtensible
|
||||||
|
HeaderProps
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseHeader creates a new header instance for use in a response
|
||||||
|
func ResponseHeader() *Header {
|
||||||
|
return new(Header)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDescription sets the description on this response, allows for chaining
|
||||||
|
func (h *Header) WithDescription(description string) *Header {
|
||||||
|
h.Description = description
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typed a fluent builder method for the type of parameter
|
||||||
|
func (h *Header) Typed(tpe, format string) *Header {
|
||||||
|
h.Type = tpe
|
||||||
|
h.Format = format
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionOf a fluent builder method for an array item
|
||||||
|
func (h *Header) CollectionOf(items *Items, format string) *Header {
|
||||||
|
h.Type = jsonArray
|
||||||
|
h.Items = items
|
||||||
|
h.CollectionFormat = format
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefault sets the default value on this item
|
||||||
|
func (h *Header) WithDefault(defaultValue interface{}) *Header {
|
||||||
|
h.Default = defaultValue
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxLength sets a max length value
|
||||||
|
func (h *Header) WithMaxLength(max int64) *Header {
|
||||||
|
h.MaxLength = &max
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinLength sets a min length value
|
||||||
|
func (h *Header) WithMinLength(min int64) *Header {
|
||||||
|
h.MinLength = &min
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPattern sets a pattern value
|
||||||
|
func (h *Header) WithPattern(pattern string) *Header {
|
||||||
|
h.Pattern = pattern
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMultipleOf sets a multiple of value
|
||||||
|
func (h *Header) WithMultipleOf(number float64) *Header {
|
||||||
|
h.MultipleOf = &number
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaximum sets a maximum number value
|
||||||
|
func (h *Header) WithMaximum(max float64, exclusive bool) *Header {
|
||||||
|
h.Maximum = &max
|
||||||
|
h.ExclusiveMaximum = exclusive
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinimum sets a minimum number value
|
||||||
|
func (h *Header) WithMinimum(min float64, exclusive bool) *Header {
|
||||||
|
h.Minimum = &min
|
||||||
|
h.ExclusiveMinimum = exclusive
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnum sets a the enum values (replace)
|
||||||
|
func (h *Header) WithEnum(values ...interface{}) *Header {
|
||||||
|
h.Enum = append([]interface{}{}, values...)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxItems sets the max items
|
||||||
|
func (h *Header) WithMaxItems(size int64) *Header {
|
||||||
|
h.MaxItems = &size
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinItems sets the min items
|
||||||
|
func (h *Header) WithMinItems(size int64) *Header {
|
||||||
|
h.MinItems = &size
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueValues dictates that this array can only have unique items
|
||||||
|
func (h *Header) UniqueValues() *Header {
|
||||||
|
h.UniqueItems = true
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowDuplicates this array can have duplicates
|
||||||
|
func (h *Header) AllowDuplicates() *Header {
|
||||||
|
h.UniqueItems = false
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValidations is a fluent method to set header validations
|
||||||
|
func (h *Header) WithValidations(val CommonValidations) *Header {
|
||||||
|
h.SetValidations(SchemaValidations{CommonValidations: val})
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshal this to JSON
|
||||||
|
func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(h.CommonValidations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(h.SimpleSchema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b3, err := json.Marshal(h.HeaderProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b1, b2, b3), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals this header from JSON
|
||||||
|
func (h *Header) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &h.CommonValidations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &h.SimpleSchema); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &h.VendorExtensible); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &h.HeaderProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (h Header) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if ex, ok := h.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := jsonpointer.GetForToken(h.CommonValidations, token)
|
||||||
|
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, _, err = jsonpointer.GetForToken(h.SimpleSchema, token)
|
||||||
|
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, _, err = jsonpointer.GetForToken(h.HeaderProps, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
165
vendor/github.com/go-openapi/spec/info.go
generated
vendored
Normal file
165
vendor/github.com/go-openapi/spec/info.go
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Extensions vendor specific extensions
|
||||||
|
type Extensions map[string]interface{}
|
||||||
|
|
||||||
|
// Add adds a value to these extensions
|
||||||
|
func (e Extensions) Add(key string, value interface{}) {
|
||||||
|
realKey := strings.ToLower(key)
|
||||||
|
e[realKey] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString gets a string value from the extensions
|
||||||
|
func (e Extensions) GetString(key string) (string, bool) {
|
||||||
|
if v, ok := e[strings.ToLower(key)]; ok {
|
||||||
|
str, ok := v.(string)
|
||||||
|
return str, ok
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool gets a string value from the extensions
|
||||||
|
func (e Extensions) GetBool(key string) (bool, bool) {
|
||||||
|
if v, ok := e[strings.ToLower(key)]; ok {
|
||||||
|
str, ok := v.(bool)
|
||||||
|
return str, ok
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringSlice gets a string value from the extensions
|
||||||
|
func (e Extensions) GetStringSlice(key string) ([]string, bool) {
|
||||||
|
if v, ok := e[strings.ToLower(key)]; ok {
|
||||||
|
arr, isSlice := v.([]interface{})
|
||||||
|
if !isSlice {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
var strs []string
|
||||||
|
for _, iface := range arr {
|
||||||
|
str, isString := iface.(string)
|
||||||
|
if !isString {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
strs = append(strs, str)
|
||||||
|
}
|
||||||
|
return strs, ok
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// VendorExtensible composition block.
|
||||||
|
type VendorExtensible struct {
|
||||||
|
Extensions Extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddExtension adds an extension to this extensible object
|
||||||
|
func (v *VendorExtensible) AddExtension(key string, value interface{}) {
|
||||||
|
if value == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v.Extensions == nil {
|
||||||
|
v.Extensions = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
v.Extensions.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the extensions to json
|
||||||
|
func (v VendorExtensible) MarshalJSON() ([]byte, error) {
|
||||||
|
toser := make(map[string]interface{})
|
||||||
|
for k, v := range v.Extensions {
|
||||||
|
lk := strings.ToLower(k)
|
||||||
|
if strings.HasPrefix(lk, "x-") {
|
||||||
|
toser[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(toser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON for this extensible object
|
||||||
|
func (v *VendorExtensible) UnmarshalJSON(data []byte) error {
|
||||||
|
var d map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, vv := range d {
|
||||||
|
lk := strings.ToLower(k)
|
||||||
|
if strings.HasPrefix(lk, "x-") {
|
||||||
|
if v.Extensions == nil {
|
||||||
|
v.Extensions = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
v.Extensions[k] = vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoProps the properties for an info definition
|
||||||
|
type InfoProps struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
TermsOfService string `json:"termsOfService,omitempty"`
|
||||||
|
Contact *ContactInfo `json:"contact,omitempty"`
|
||||||
|
License *License `json:"license,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info object provides metadata about the API.
|
||||||
|
// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#infoObject
|
||||||
|
type Info struct {
|
||||||
|
VendorExtensible
|
||||||
|
InfoProps
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (i Info) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if ex, ok := i.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
r, _, err := jsonpointer.GetForToken(i.InfoProps, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshal this to JSON
|
||||||
|
func (i Info) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(i.InfoProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(i.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b1, b2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON marshal this from JSON
|
||||||
|
func (i *Info) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &i.InfoProps); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &i.VendorExtensible)
|
||||||
|
}
|
||||||
234
vendor/github.com/go-openapi/spec/items.go
generated
vendored
Normal file
234
vendor/github.com/go-openapi/spec/items.go
generated
vendored
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsonRef = "$ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SimpleSchema describe swagger simple schemas for parameters and headers
|
||||||
|
type SimpleSchema struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Nullable bool `json:"nullable,omitempty"`
|
||||||
|
Format string `json:"format,omitempty"`
|
||||||
|
Items *Items `json:"items,omitempty"`
|
||||||
|
CollectionFormat string `json:"collectionFormat,omitempty"`
|
||||||
|
Default interface{} `json:"default,omitempty"`
|
||||||
|
Example interface{} `json:"example,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName return the type (or format) of a simple schema
|
||||||
|
func (s *SimpleSchema) TypeName() string {
|
||||||
|
if s.Format != "" {
|
||||||
|
return s.Format
|
||||||
|
}
|
||||||
|
return s.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemsTypeName yields the type of items in a simple schema array
|
||||||
|
func (s *SimpleSchema) ItemsTypeName() string {
|
||||||
|
if s.Items == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.Items.TypeName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items a limited subset of JSON-Schema's items object.
|
||||||
|
// It is used by parameter definitions that are not located in "body".
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#items-object
|
||||||
|
type Items struct {
|
||||||
|
Refable
|
||||||
|
CommonValidations
|
||||||
|
SimpleSchema
|
||||||
|
VendorExtensible
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewItems creates a new instance of items
|
||||||
|
func NewItems() *Items {
|
||||||
|
return &Items{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typed a fluent builder method for the type of item
|
||||||
|
func (i *Items) Typed(tpe, format string) *Items {
|
||||||
|
i.Type = tpe
|
||||||
|
i.Format = format
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsNullable flags this schema as nullable.
|
||||||
|
func (i *Items) AsNullable() *Items {
|
||||||
|
i.Nullable = true
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionOf a fluent builder method for an array item
|
||||||
|
func (i *Items) CollectionOf(items *Items, format string) *Items {
|
||||||
|
i.Type = jsonArray
|
||||||
|
i.Items = items
|
||||||
|
i.CollectionFormat = format
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefault sets the default value on this item
|
||||||
|
func (i *Items) WithDefault(defaultValue interface{}) *Items {
|
||||||
|
i.Default = defaultValue
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxLength sets a max length value
|
||||||
|
func (i *Items) WithMaxLength(max int64) *Items {
|
||||||
|
i.MaxLength = &max
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinLength sets a min length value
|
||||||
|
func (i *Items) WithMinLength(min int64) *Items {
|
||||||
|
i.MinLength = &min
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPattern sets a pattern value
|
||||||
|
func (i *Items) WithPattern(pattern string) *Items {
|
||||||
|
i.Pattern = pattern
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMultipleOf sets a multiple of value
|
||||||
|
func (i *Items) WithMultipleOf(number float64) *Items {
|
||||||
|
i.MultipleOf = &number
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaximum sets a maximum number value
|
||||||
|
func (i *Items) WithMaximum(max float64, exclusive bool) *Items {
|
||||||
|
i.Maximum = &max
|
||||||
|
i.ExclusiveMaximum = exclusive
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinimum sets a minimum number value
|
||||||
|
func (i *Items) WithMinimum(min float64, exclusive bool) *Items {
|
||||||
|
i.Minimum = &min
|
||||||
|
i.ExclusiveMinimum = exclusive
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnum sets a the enum values (replace)
|
||||||
|
func (i *Items) WithEnum(values ...interface{}) *Items {
|
||||||
|
i.Enum = append([]interface{}{}, values...)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxItems sets the max items
|
||||||
|
func (i *Items) WithMaxItems(size int64) *Items {
|
||||||
|
i.MaxItems = &size
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinItems sets the min items
|
||||||
|
func (i *Items) WithMinItems(size int64) *Items {
|
||||||
|
i.MinItems = &size
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueValues dictates that this array can only have unique items
|
||||||
|
func (i *Items) UniqueValues() *Items {
|
||||||
|
i.UniqueItems = true
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowDuplicates this array can have duplicates
|
||||||
|
func (i *Items) AllowDuplicates() *Items {
|
||||||
|
i.UniqueItems = false
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValidations is a fluent method to set Items validations
|
||||||
|
func (i *Items) WithValidations(val CommonValidations) *Items {
|
||||||
|
i.SetValidations(SchemaValidations{CommonValidations: val})
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||||
|
func (i *Items) UnmarshalJSON(data []byte) error {
|
||||||
|
var validations CommonValidations
|
||||||
|
if err := json.Unmarshal(data, &validations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ref Refable
|
||||||
|
if err := json.Unmarshal(data, &ref); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var simpleSchema SimpleSchema
|
||||||
|
if err := json.Unmarshal(data, &simpleSchema); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var vendorExtensible VendorExtensible
|
||||||
|
if err := json.Unmarshal(data, &vendorExtensible); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Refable = ref
|
||||||
|
i.CommonValidations = validations
|
||||||
|
i.SimpleSchema = simpleSchema
|
||||||
|
i.VendorExtensible = vendorExtensible
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts this items object to JSON
|
||||||
|
func (i Items) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(i.CommonValidations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(i.SimpleSchema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b3, err := json.Marshal(i.Refable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b4, err := json.Marshal(i.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b4, b3, b1, b2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (i Items) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if token == jsonRef {
|
||||||
|
return &i.Ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := jsonpointer.GetForToken(i.CommonValidations, token)
|
||||||
|
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, _, err = jsonpointer.GetForToken(i.SimpleSchema, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
56
vendor/github.com/go-openapi/spec/license.go
generated
vendored
Normal file
56
vendor/github.com/go-openapi/spec/license.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// License information for the exposed API.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#licenseObject
|
||||||
|
type License struct {
|
||||||
|
LicenseProps
|
||||||
|
VendorExtensible
|
||||||
|
}
|
||||||
|
|
||||||
|
// LicenseProps holds the properties of a License object
|
||||||
|
type LicenseProps struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates License from json
|
||||||
|
func (l *License) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &l.LicenseProps); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &l.VendorExtensible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON produces License as json
|
||||||
|
func (l License) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(l.LicenseProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(l.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b1, b2), nil
|
||||||
|
}
|
||||||
203
vendor/github.com/go-openapi/spec/normalizer.go
generated
vendored
Normal file
203
vendor/github.com/go-openapi/spec/normalizer.go
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileScheme = "file"
|
||||||
|
|
||||||
|
// normalizeURI ensures that all $ref paths used internally by the expander are canonicalized.
|
||||||
|
//
|
||||||
|
// NOTE(windows): there is a tolerance over the strict URI format on windows.
|
||||||
|
//
|
||||||
|
// The normalizer accepts relative file URLs like 'Path\File.JSON' as well as absolute file URLs like
|
||||||
|
// 'C:\Path\file.Yaml'.
|
||||||
|
//
|
||||||
|
// Both are canonicalized with a "file://" scheme, slashes and a lower-cased path:
|
||||||
|
// 'file:///c:/path/file.yaml'
|
||||||
|
//
|
||||||
|
// URLs can be specified with a file scheme, like in 'file:///folder/file.json' or
|
||||||
|
// 'file:///c:\folder\File.json'.
|
||||||
|
//
|
||||||
|
// URLs like file://C:\folder are considered invalid (i.e. there is no host 'c:\folder') and a "repair"
|
||||||
|
// is attempted.
|
||||||
|
//
|
||||||
|
// The base path argument is assumed to be canonicalized (e.g. using normalizeBase()).
|
||||||
|
func normalizeURI(refPath, base string) string {
|
||||||
|
refURL, err := url.Parse(refPath)
|
||||||
|
if err != nil {
|
||||||
|
specLogger.Printf("warning: invalid URI in $ref %q: %v", refPath, err)
|
||||||
|
refURL, refPath = repairURI(refPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixWindowsURI(refURL, refPath) // noop on non-windows OS
|
||||||
|
|
||||||
|
refURL.Path = path.Clean(refURL.Path)
|
||||||
|
if refURL.Path == "." {
|
||||||
|
refURL.Path = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
r := MustCreateRef(refURL.String())
|
||||||
|
if r.IsCanonical() {
|
||||||
|
return refURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL, _ := url.Parse(base)
|
||||||
|
if path.IsAbs(refURL.Path) {
|
||||||
|
baseURL.Path = refURL.Path
|
||||||
|
} else if refURL.Path != "" {
|
||||||
|
baseURL.Path = path.Join(path.Dir(baseURL.Path), refURL.Path)
|
||||||
|
}
|
||||||
|
// copying fragment from ref to base
|
||||||
|
baseURL.Fragment = refURL.Fragment
|
||||||
|
|
||||||
|
return baseURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// denormalizeRef returns the simplest notation for a normalized $ref, given the path of the original root document.
|
||||||
|
//
|
||||||
|
// When calling this, we assume that:
|
||||||
|
// * $ref is a canonical URI
|
||||||
|
// * originalRelativeBase is a canonical URI
|
||||||
|
//
|
||||||
|
// denormalizeRef is currently used when we rewrite a $ref after a circular $ref has been detected.
|
||||||
|
// In this case, expansion stops and normally renders the internal canonical $ref.
|
||||||
|
//
|
||||||
|
// This internal $ref is eventually rebased to the original RelativeBase used for the expansion.
|
||||||
|
//
|
||||||
|
// There is a special case for schemas that are anchored with an "id":
|
||||||
|
// in that case, the rebasing is performed // against the id only if this is an anchor for the initial root document.
|
||||||
|
// All other intermediate "id"'s found along the way are ignored for the purpose of rebasing.
|
||||||
|
//
|
||||||
|
func denormalizeRef(ref *Ref, originalRelativeBase, id string) Ref {
|
||||||
|
debugLog("denormalizeRef called:\n$ref: %q\noriginal: %s\nroot ID:%s", ref.String(), originalRelativeBase, id)
|
||||||
|
|
||||||
|
if ref.String() == "" || ref.IsRoot() || ref.HasFragmentOnly {
|
||||||
|
// short circuit: $ref to current doc
|
||||||
|
return *ref
|
||||||
|
}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
idBaseURL, err := url.Parse(id)
|
||||||
|
if err == nil { // if the schema id is not usable as a URI, ignore it
|
||||||
|
if ref, ok := rebase(ref, idBaseURL, true); ok { // rebase, but keep references to root unchaged (do not want $ref: "")
|
||||||
|
// $ref relative to the ID of the schema in the root document
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRelativeBaseURL, _ := url.Parse(originalRelativeBase)
|
||||||
|
|
||||||
|
r, _ := rebase(ref, originalRelativeBaseURL, false)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) {
|
||||||
|
var newBase url.URL
|
||||||
|
|
||||||
|
u := ref.GetURL()
|
||||||
|
|
||||||
|
if u.Scheme != v.Scheme || u.Host != v.Host {
|
||||||
|
return *ref, false
|
||||||
|
}
|
||||||
|
|
||||||
|
docPath := v.Path
|
||||||
|
v.Path = path.Dir(v.Path)
|
||||||
|
|
||||||
|
if v.Path == "." {
|
||||||
|
v.Path = ""
|
||||||
|
} else if !strings.HasSuffix(v.Path, "/") {
|
||||||
|
v.Path += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
newBase.Fragment = u.Fragment
|
||||||
|
|
||||||
|
if strings.HasPrefix(u.Path, docPath) {
|
||||||
|
newBase.Path = strings.TrimPrefix(u.Path, docPath)
|
||||||
|
} else {
|
||||||
|
newBase.Path = strings.TrimPrefix(u.Path, v.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if notEqual && newBase.Path == "" && newBase.Fragment == "" {
|
||||||
|
// do not want rebasing to end up in an empty $ref
|
||||||
|
return *ref, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.IsAbs(newBase.Path) {
|
||||||
|
// whenever we end up with an absolute path, specify the scheme and host
|
||||||
|
newBase.Scheme = v.Scheme
|
||||||
|
newBase.Host = v.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
return MustCreateRef(newBase.String()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeRef canonicalize a Ref, using a canonical relativeBase as its absolute anchor
|
||||||
|
func normalizeRef(ref *Ref, relativeBase string) *Ref {
|
||||||
|
r := MustCreateRef(normalizeURI(ref.String(), relativeBase))
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeBase performs a normalization of the input base path.
|
||||||
|
//
|
||||||
|
// This always yields a canonical URI (absolute), usable for the document cache.
|
||||||
|
//
|
||||||
|
// It ensures that all further internal work on basePath may safely assume
|
||||||
|
// a non-empty, cross-platform, canonical URI (i.e. absolute).
|
||||||
|
//
|
||||||
|
// This normalization tolerates windows paths (e.g. C:\x\y\File.dat) and transform this
|
||||||
|
// in a file:// URL with lower cased drive letter and path.
|
||||||
|
//
|
||||||
|
// See also: https://en.wikipedia.org/wiki/File_URI_scheme
|
||||||
|
func normalizeBase(in string) string {
|
||||||
|
u, err := url.Parse(in)
|
||||||
|
if err != nil {
|
||||||
|
specLogger.Printf("warning: invalid URI in RelativeBase %q: %v", in, err)
|
||||||
|
u, in = repairURI(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Fragment = "" // any fragment in the base is irrelevant
|
||||||
|
|
||||||
|
fixWindowsURI(u, in) // noop on non-windows OS
|
||||||
|
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
if u.Path == "." { // empty after Clean()
|
||||||
|
u.Path = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "" {
|
||||||
|
if path.IsAbs(u.Path) || u.Scheme != fileScheme {
|
||||||
|
// this is absolute or explicitly not a local file: we're good
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no scheme or file scheme with relative path: assume file and make it absolute
|
||||||
|
// enforce scheme file://... with absolute path.
|
||||||
|
//
|
||||||
|
// If the input path is relative, we anchor the path to the current working directory.
|
||||||
|
// NOTE: we may end up with a host component. Leave it unchanged: e.g. file://host/folder/file.json
|
||||||
|
|
||||||
|
u.Scheme = fileScheme
|
||||||
|
u.Path = absPath(u.Path) // platform-dependent
|
||||||
|
u.RawQuery = "" // any query component is irrelevant for a base
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
43
vendor/github.com/go-openapi/spec/normalizer_nonwindows.go
generated
vendored
Normal file
43
vendor/github.com/go-openapi/spec/normalizer_nonwindows.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// absPath makes a file path absolute and compatible with a URI path component.
|
||||||
|
//
|
||||||
|
// The parameter must be a path, not an URI.
|
||||||
|
func absPath(in string) string {
|
||||||
|
anchored, err := filepath.Abs(in)
|
||||||
|
if err != nil {
|
||||||
|
specLogger.Printf("warning: could not resolve current working directory: %v", err)
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
return anchored
|
||||||
|
}
|
||||||
|
|
||||||
|
func repairURI(in string) (*url.URL, string) {
|
||||||
|
u, _ := url.Parse("")
|
||||||
|
debugLog("repaired URI: original: %q, repaired: %q", in, "")
|
||||||
|
return u, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixWindowsURI(u *url.URL, in string) {
|
||||||
|
}
|
||||||
154
vendor/github.com/go-openapi/spec/normalizer_windows.go
generated
vendored
Normal file
154
vendor/github.com/go-openapi/spec/normalizer_windows.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// -build windows
|
||||||
|
|
||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// absPath makes a file path absolute and compatible with a URI path component
|
||||||
|
//
|
||||||
|
// The parameter must be a path, not an URI.
|
||||||
|
func absPath(in string) string {
|
||||||
|
// NOTE(windows): filepath.Abs exhibits a special behavior on windows for empty paths.
|
||||||
|
// See https://github.com/golang/go/issues/24441
|
||||||
|
if in == "" {
|
||||||
|
in = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
anchored, err := filepath.Abs(in)
|
||||||
|
if err != nil {
|
||||||
|
specLogger.Printf("warning: could not resolve current working directory: %v", err)
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
pth := strings.ReplaceAll(strings.ToLower(anchored), `\`, `/`)
|
||||||
|
if !strings.HasPrefix(pth, "/") {
|
||||||
|
pth = "/" + pth
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Clean(pth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// repairURI tolerates invalid file URIs with common typos
|
||||||
|
// such as 'file://E:\folder\file', that break the regular URL parser.
|
||||||
|
//
|
||||||
|
// Adopting the same defaults as for unixes (e.g. return an empty path) would
|
||||||
|
// result into a counter-intuitive result for that case (e.g. E:\folder\file is
|
||||||
|
// eventually resolved as the current directory). The repair will detect the missing "/".
|
||||||
|
//
|
||||||
|
// Note that this only works for the file scheme.
|
||||||
|
func repairURI(in string) (*url.URL, string) {
|
||||||
|
const prefix = fileScheme + "://"
|
||||||
|
if !strings.HasPrefix(in, prefix) {
|
||||||
|
// giving up: resolve to empty path
|
||||||
|
u, _ := url.Parse("")
|
||||||
|
|
||||||
|
return u, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt the repair, stripping the scheme should be sufficient
|
||||||
|
u, _ := url.Parse(strings.TrimPrefix(in, prefix))
|
||||||
|
debugLog("repaired URI: original: %q, repaired: %q", in, u.String())
|
||||||
|
|
||||||
|
return u, u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixWindowsURI tolerates an absolute file path on windows such as C:\Base\File.yaml or \\host\share\Base\File.yaml
|
||||||
|
// and makes it a canonical URI: file:///c:/base/file.yaml
|
||||||
|
//
|
||||||
|
// Catch 22 notes for Windows:
|
||||||
|
//
|
||||||
|
// * There may be a drive letter on windows (it is lower-cased)
|
||||||
|
// * There may be a share UNC, e.g. \\server\folder\data.xml
|
||||||
|
// * Paths are case insensitive
|
||||||
|
// * Paths may already contain slashes
|
||||||
|
// * Paths must be slashed
|
||||||
|
//
|
||||||
|
// NOTE: there is no escaping. "/" may be valid separators just like "\".
|
||||||
|
// We don't use ToSlash() (which escapes everything) because windows now also
|
||||||
|
// tolerates the use of "/". Hence, both C:\File.yaml and C:/File.yaml will work.
|
||||||
|
func fixWindowsURI(u *url.URL, in string) {
|
||||||
|
drive := filepath.VolumeName(in)
|
||||||
|
|
||||||
|
if len(drive) > 0 {
|
||||||
|
if len(u.Scheme) == 1 && strings.EqualFold(u.Scheme, drive[:1]) { // a path with a drive letter
|
||||||
|
u.Scheme = fileScheme
|
||||||
|
u.Host = ""
|
||||||
|
u.Path = strings.Join([]string{drive, u.Opaque, u.Path}, `/`) // reconstruct the full path component (no fragment, no query)
|
||||||
|
} else if u.Host == "" && strings.HasPrefix(u.Path, drive) { // a path with a \\host volume
|
||||||
|
// NOTE: the special host@port syntax for UNC is not supported (yet)
|
||||||
|
u.Scheme = fileScheme
|
||||||
|
|
||||||
|
// this is a modified version of filepath.Dir() to apply on the VolumeName itself
|
||||||
|
i := len(drive) - 1
|
||||||
|
for i >= 0 && !os.IsPathSeparator(drive[i]) {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
host := drive[:i] // \\host\share => host
|
||||||
|
|
||||||
|
u.Path = strings.TrimPrefix(u.Path, host)
|
||||||
|
u.Host = strings.TrimPrefix(host, `\\`)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Opaque = ""
|
||||||
|
u.Path = strings.ReplaceAll(strings.ToLower(u.Path), `\`, `/`)
|
||||||
|
|
||||||
|
// ensure we form an absolute path
|
||||||
|
if !strings.HasPrefix(u.Path, "/") {
|
||||||
|
u.Path = "/" + u.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == fileScheme {
|
||||||
|
// Handle dodgy cases for file://{...} URIs on windows.
|
||||||
|
// A canonical URI should always be followed by an absolute path.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// * file:///folder/file => valid, unchanged
|
||||||
|
// * file:///c:\folder\file => slashed
|
||||||
|
// * file:///./folder/file => valid, cleaned to remove the dot
|
||||||
|
// * file:///.\folder\file => remapped to cwd
|
||||||
|
// * file:///. => dodgy, remapped to / (consistent with the behavior on unix)
|
||||||
|
// * file:///.. => dodgy, remapped to / (consistent with the behavior on unix)
|
||||||
|
if (!path.IsAbs(u.Path) && !filepath.IsAbs(u.Path)) || (strings.HasPrefix(u.Path, `/.`) && strings.Contains(u.Path, `\`)) {
|
||||||
|
// ensure we form an absolute path
|
||||||
|
u.Path, _ = filepath.Abs(strings.TrimLeft(u.Path, `/`))
|
||||||
|
if !strings.HasPrefix(u.Path, "/") {
|
||||||
|
u.Path = "/" + u.Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.Path = strings.ToLower(u.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: lower case normalization does not propagate to inner resources,
|
||||||
|
// generated when rebasing: when joining a relative URI with a file to an absolute base,
|
||||||
|
// only the base is currently lower-cased.
|
||||||
|
//
|
||||||
|
// For now, we assume this is good enough for most use cases
|
||||||
|
// and try not to generate too many differences
|
||||||
|
// between the output produced on different platforms.
|
||||||
|
u.Path = path.Clean(strings.ReplaceAll(u.Path, `\`, `/`))
|
||||||
|
}
|
||||||
397
vendor/github.com/go-openapi/spec/operation.go
generated
vendored
Normal file
397
vendor/github.com/go-openapi/spec/operation.go
generated
vendored
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(map[string]interface{}{})
|
||||||
|
gob.Register([]interface{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperationProps describes an operation
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
// - schemes, when present must be from [http, https, ws, wss]: see validate
|
||||||
|
// - Security is handled as a special case: see MarshalJSON function
|
||||||
|
type OperationProps struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Consumes []string `json:"consumes,omitempty"`
|
||||||
|
Produces []string `json:"produces,omitempty"`
|
||||||
|
Schemes []string `json:"schemes,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Summary string `json:"summary,omitempty"`
|
||||||
|
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
|
||||||
|
ID string `json:"operationId,omitempty"`
|
||||||
|
Deprecated bool `json:"deprecated,omitempty"`
|
||||||
|
Security []map[string][]string `json:"security,omitempty"`
|
||||||
|
Parameters []Parameter `json:"parameters,omitempty"`
|
||||||
|
Responses *Responses `json:"responses,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON takes care of serializing operation properties to JSON
|
||||||
|
//
|
||||||
|
// We use a custom marhaller here to handle a special cases related to
|
||||||
|
// the Security field. We need to preserve zero length slice
|
||||||
|
// while omitting the field when the value is nil/unset.
|
||||||
|
func (op OperationProps) MarshalJSON() ([]byte, error) {
|
||||||
|
type Alias OperationProps
|
||||||
|
if op.Security == nil {
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
Security []map[string][]string `json:"security,omitempty"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Security: op.Security,
|
||||||
|
Alias: (*Alias)(&op),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
Security []map[string][]string `json:"security"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Security: op.Security,
|
||||||
|
Alias: (*Alias)(&op),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation describes a single API operation on a path.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#operationObject
|
||||||
|
type Operation struct {
|
||||||
|
VendorExtensible
|
||||||
|
OperationProps
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuccessResponse gets a success response model
|
||||||
|
func (o *Operation) SuccessResponse() (*Response, int, bool) {
|
||||||
|
if o.Responses == nil {
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCodes := make([]int, 0, len(o.Responses.StatusCodeResponses))
|
||||||
|
for k := range o.Responses.StatusCodeResponses {
|
||||||
|
if k >= 200 && k < 300 {
|
||||||
|
responseCodes = append(responseCodes, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(responseCodes) > 0 {
|
||||||
|
sort.Ints(responseCodes)
|
||||||
|
v := o.Responses.StatusCodeResponses[responseCodes[0]]
|
||||||
|
return &v, responseCodes[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.Responses.Default, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (o Operation) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if ex, ok := o.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
r, _, err := jsonpointer.GetForToken(o.OperationProps, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||||
|
func (o *Operation) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &o.OperationProps); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &o.VendorExtensible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts this items object to JSON
|
||||||
|
func (o Operation) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(o.OperationProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(o.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
concated := swag.ConcatJSON(b1, b2)
|
||||||
|
return concated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOperation creates a new operation instance.
|
||||||
|
// It expects an ID as parameter but not passing an ID is also valid.
|
||||||
|
func NewOperation(id string) *Operation {
|
||||||
|
op := new(Operation)
|
||||||
|
op.ID = id
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithID sets the ID property on this operation, allows for chaining.
|
||||||
|
func (o *Operation) WithID(id string) *Operation {
|
||||||
|
o.ID = id
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDescription sets the description on this operation, allows for chaining
|
||||||
|
func (o *Operation) WithDescription(description string) *Operation {
|
||||||
|
o.Description = description
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSummary sets the summary on this operation, allows for chaining
|
||||||
|
func (o *Operation) WithSummary(summary string) *Operation {
|
||||||
|
o.Summary = summary
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExternalDocs sets/removes the external docs for/from this operation.
|
||||||
|
// When you pass empty strings as params the external documents will be removed.
|
||||||
|
// When you pass non-empty string as one value then those values will be used on the external docs object.
|
||||||
|
// So when you pass a non-empty description, you should also pass the url and vice versa.
|
||||||
|
func (o *Operation) WithExternalDocs(description, url string) *Operation {
|
||||||
|
if description == "" && url == "" {
|
||||||
|
o.ExternalDocs = nil
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.ExternalDocs == nil {
|
||||||
|
o.ExternalDocs = &ExternalDocumentation{}
|
||||||
|
}
|
||||||
|
o.ExternalDocs.Description = description
|
||||||
|
o.ExternalDocs.URL = url
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecate marks the operation as deprecated
|
||||||
|
func (o *Operation) Deprecate() *Operation {
|
||||||
|
o.Deprecated = true
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undeprecate marks the operation as not deprected
|
||||||
|
func (o *Operation) Undeprecate() *Operation {
|
||||||
|
o.Deprecated = false
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConsumes adds media types for incoming body values
|
||||||
|
func (o *Operation) WithConsumes(mediaTypes ...string) *Operation {
|
||||||
|
o.Consumes = append(o.Consumes, mediaTypes...)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithProduces adds media types for outgoing body values
|
||||||
|
func (o *Operation) WithProduces(mediaTypes ...string) *Operation {
|
||||||
|
o.Produces = append(o.Produces, mediaTypes...)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTags adds tags for this operation
|
||||||
|
func (o *Operation) WithTags(tags ...string) *Operation {
|
||||||
|
o.Tags = append(o.Tags, tags...)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddParam adds a parameter to this operation, when a parameter for that location
|
||||||
|
// and with that name already exists it will be replaced
|
||||||
|
func (o *Operation) AddParam(param *Parameter) *Operation {
|
||||||
|
if param == nil {
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range o.Parameters {
|
||||||
|
if p.Name == param.Name && p.In == param.In {
|
||||||
|
params := append(o.Parameters[:i], *param)
|
||||||
|
params = append(params, o.Parameters[i+1:]...)
|
||||||
|
o.Parameters = params
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Parameters = append(o.Parameters, *param)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveParam removes a parameter from the operation
|
||||||
|
func (o *Operation) RemoveParam(name, in string) *Operation {
|
||||||
|
for i, p := range o.Parameters {
|
||||||
|
if p.Name == name && p.In == in {
|
||||||
|
o.Parameters = append(o.Parameters[:i], o.Parameters[i+1:]...)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecuredWith adds a security scope to this operation.
|
||||||
|
func (o *Operation) SecuredWith(name string, scopes ...string) *Operation {
|
||||||
|
o.Security = append(o.Security, map[string][]string{name: scopes})
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultResponse adds a default response to the operation.
|
||||||
|
// Passing a nil value will remove the response
|
||||||
|
func (o *Operation) WithDefaultResponse(response *Response) *Operation {
|
||||||
|
return o.RespondsWith(0, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespondsWith adds a status code response to the operation.
|
||||||
|
// When the code is 0 the value of the response will be used as default response value.
|
||||||
|
// When the value of the response is nil it will be removed from the operation
|
||||||
|
func (o *Operation) RespondsWith(code int, response *Response) *Operation {
|
||||||
|
if o.Responses == nil {
|
||||||
|
o.Responses = new(Responses)
|
||||||
|
}
|
||||||
|
if code == 0 {
|
||||||
|
o.Responses.Default = response
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
if response == nil {
|
||||||
|
delete(o.Responses.StatusCodeResponses, code)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
if o.Responses.StatusCodeResponses == nil {
|
||||||
|
o.Responses.StatusCodeResponses = make(map[int]Response)
|
||||||
|
}
|
||||||
|
o.Responses.StatusCodeResponses[code] = *response
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
type opsAlias OperationProps
|
||||||
|
|
||||||
|
type gobAlias struct {
|
||||||
|
Security []map[string]struct {
|
||||||
|
List []string
|
||||||
|
Pad bool
|
||||||
|
}
|
||||||
|
Alias *opsAlias
|
||||||
|
SecurityIsEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode provides a safe gob encoder for Operation, including empty security requirements
|
||||||
|
func (o Operation) GobEncode() ([]byte, error) {
|
||||||
|
raw := struct {
|
||||||
|
Ext VendorExtensible
|
||||||
|
Props OperationProps
|
||||||
|
}{
|
||||||
|
Ext: o.VendorExtensible,
|
||||||
|
Props: o.OperationProps,
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := gob.NewEncoder(&b).Encode(raw)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode provides a safe gob decoder for Operation, including empty security requirements
|
||||||
|
func (o *Operation) GobDecode(b []byte) error {
|
||||||
|
var raw struct {
|
||||||
|
Ext VendorExtensible
|
||||||
|
Props OperationProps
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
err := gob.NewDecoder(buf).Decode(&raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.VendorExtensible = raw.Ext
|
||||||
|
o.OperationProps = raw.Props
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode provides a safe gob encoder for Operation, including empty security requirements
|
||||||
|
func (op OperationProps) GobEncode() ([]byte, error) {
|
||||||
|
raw := gobAlias{
|
||||||
|
Alias: (*opsAlias)(&op),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if op.Security == nil {
|
||||||
|
// nil security requirement
|
||||||
|
err := gob.NewEncoder(&b).Encode(raw)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(op.Security) == 0 {
|
||||||
|
// empty, but non-nil security requirement
|
||||||
|
raw.SecurityIsEmpty = true
|
||||||
|
raw.Alias.Security = nil
|
||||||
|
err := gob.NewEncoder(&b).Encode(raw)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.Security = make([]map[string]struct {
|
||||||
|
List []string
|
||||||
|
Pad bool
|
||||||
|
}, 0, len(op.Security))
|
||||||
|
for _, req := range op.Security {
|
||||||
|
v := make(map[string]struct {
|
||||||
|
List []string
|
||||||
|
Pad bool
|
||||||
|
}, len(req))
|
||||||
|
for k, val := range req {
|
||||||
|
v[k] = struct {
|
||||||
|
List []string
|
||||||
|
Pad bool
|
||||||
|
}{
|
||||||
|
List: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw.Security = append(raw.Security, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := gob.NewEncoder(&b).Encode(raw)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode provides a safe gob decoder for Operation, including empty security requirements
|
||||||
|
func (op *OperationProps) GobDecode(b []byte) error {
|
||||||
|
var raw gobAlias
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
err := gob.NewDecoder(buf).Decode(&raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if raw.Alias == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case raw.SecurityIsEmpty:
|
||||||
|
// empty, but non-nil security requirement
|
||||||
|
raw.Alias.Security = []map[string][]string{}
|
||||||
|
case len(raw.Alias.Security) == 0:
|
||||||
|
// nil security requirement
|
||||||
|
raw.Alias.Security = nil
|
||||||
|
default:
|
||||||
|
raw.Alias.Security = make([]map[string][]string, 0, len(raw.Security))
|
||||||
|
for _, req := range raw.Security {
|
||||||
|
v := make(map[string][]string, len(req))
|
||||||
|
for k, val := range req {
|
||||||
|
v[k] = make([]string, 0, len(val.List))
|
||||||
|
v[k] = append(v[k], val.List...)
|
||||||
|
}
|
||||||
|
raw.Alias.Security = append(raw.Alias.Security, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*op = *(*OperationProps)(raw.Alias)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
326
vendor/github.com/go-openapi/spec/parameter.go
generated
vendored
Normal file
326
vendor/github.com/go-openapi/spec/parameter.go
generated
vendored
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryParam creates a query parameter
|
||||||
|
func QueryParam(name string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "query"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderParam creates a header parameter, this is always required by default
|
||||||
|
func HeaderParam(name string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "header", Required: true}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathParam creates a path parameter, this is always required
|
||||||
|
func PathParam(name string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "path", Required: true}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyParam creates a body parameter
|
||||||
|
func BodyParam(name string, schema *Schema) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "body", Schema: schema}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormDataParam creates a body parameter
|
||||||
|
func FormDataParam(name string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileParam creates a body parameter
|
||||||
|
func FileParam(name string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name, In: "formData"},
|
||||||
|
SimpleSchema: SimpleSchema{Type: "file"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleArrayParam creates a param for a simple array (string, int, date etc)
|
||||||
|
func SimpleArrayParam(name, tpe, fmt string) *Parameter {
|
||||||
|
return &Parameter{ParamProps: ParamProps{Name: name},
|
||||||
|
SimpleSchema: SimpleSchema{Type: jsonArray, CollectionFormat: "csv",
|
||||||
|
Items: &Items{SimpleSchema: SimpleSchema{Type: tpe, Format: fmt}}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamRef creates a parameter that's a json reference
|
||||||
|
func ParamRef(uri string) *Parameter {
|
||||||
|
p := new(Parameter)
|
||||||
|
p.Ref = MustCreateRef(uri)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamProps describes the specific attributes of an operation parameter
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
// - Schema is defined when "in" == "body": see validate
|
||||||
|
// - AllowEmptyValue is allowed where "in" == "query" || "formData"
|
||||||
|
type ParamProps struct {
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
In string `json:"in,omitempty"`
|
||||||
|
Required bool `json:"required,omitempty"`
|
||||||
|
Schema *Schema `json:"schema,omitempty"`
|
||||||
|
AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
|
||||||
|
//
|
||||||
|
// There are five possible parameter types.
|
||||||
|
// * Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part
|
||||||
|
// of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`,
|
||||||
|
// the path parameter is `itemId`.
|
||||||
|
// * Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`.
|
||||||
|
// * Header - Custom headers that are expected as part of the request.
|
||||||
|
// * Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be
|
||||||
|
// _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for
|
||||||
|
// documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist
|
||||||
|
// together for the same operation.
|
||||||
|
// * Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or
|
||||||
|
// `multipart/form-data` are used as the content type of the request (in Swagger's definition,
|
||||||
|
// the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used
|
||||||
|
// to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be
|
||||||
|
// declared together with a body parameter for the same operation. Form parameters have a different format based on
|
||||||
|
// the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4).
|
||||||
|
// * `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload.
|
||||||
|
// For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple
|
||||||
|
// parameters that are being transferred.
|
||||||
|
// * `multipart/form-data` - each parameter takes a section in the payload with an internal header.
|
||||||
|
// For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is
|
||||||
|
// `submit-name`. This type of form parameters is more commonly used for file transfers.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#parameterObject
|
||||||
|
type Parameter struct {
|
||||||
|
Refable
|
||||||
|
CommonValidations
|
||||||
|
SimpleSchema
|
||||||
|
VendorExtensible
|
||||||
|
ParamProps
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (p Parameter) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if ex, ok := p.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
if token == jsonRef {
|
||||||
|
return &p.Ref, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := jsonpointer.GetForToken(p.CommonValidations, token)
|
||||||
|
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, _, err = jsonpointer.GetForToken(p.SimpleSchema, token)
|
||||||
|
if err != nil && !strings.HasPrefix(err.Error(), "object has no field") {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r != nil {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
r, _, err = jsonpointer.GetForToken(p.ParamProps, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDescription a fluent builder method for the description of the parameter
|
||||||
|
func (p *Parameter) WithDescription(description string) *Parameter {
|
||||||
|
p.Description = description
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named a fluent builder method to override the name of the parameter
|
||||||
|
func (p *Parameter) Named(name string) *Parameter {
|
||||||
|
p.Name = name
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLocation a fluent builder method to override the location of the parameter
|
||||||
|
func (p *Parameter) WithLocation(in string) *Parameter {
|
||||||
|
p.In = in
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typed a fluent builder method for the type of the parameter value
|
||||||
|
func (p *Parameter) Typed(tpe, format string) *Parameter {
|
||||||
|
p.Type = tpe
|
||||||
|
p.Format = format
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionOf a fluent builder method for an array parameter
|
||||||
|
func (p *Parameter) CollectionOf(items *Items, format string) *Parameter {
|
||||||
|
p.Type = jsonArray
|
||||||
|
p.Items = items
|
||||||
|
p.CollectionFormat = format
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefault sets the default value on this parameter
|
||||||
|
func (p *Parameter) WithDefault(defaultValue interface{}) *Parameter {
|
||||||
|
p.AsOptional() // with default implies optional
|
||||||
|
p.Default = defaultValue
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowsEmptyValues flags this parameter as being ok with empty values
|
||||||
|
func (p *Parameter) AllowsEmptyValues() *Parameter {
|
||||||
|
p.AllowEmptyValue = true
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoEmptyValues flags this parameter as not liking empty values
|
||||||
|
func (p *Parameter) NoEmptyValues() *Parameter {
|
||||||
|
p.AllowEmptyValue = false
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsOptional flags this parameter as optional
|
||||||
|
func (p *Parameter) AsOptional() *Parameter {
|
||||||
|
p.Required = false
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRequired flags this parameter as required
|
||||||
|
func (p *Parameter) AsRequired() *Parameter {
|
||||||
|
if p.Default != nil { // with a default required makes no sense
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
p.Required = true
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxLength sets a max length value
|
||||||
|
func (p *Parameter) WithMaxLength(max int64) *Parameter {
|
||||||
|
p.MaxLength = &max
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinLength sets a min length value
|
||||||
|
func (p *Parameter) WithMinLength(min int64) *Parameter {
|
||||||
|
p.MinLength = &min
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPattern sets a pattern value
|
||||||
|
func (p *Parameter) WithPattern(pattern string) *Parameter {
|
||||||
|
p.Pattern = pattern
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMultipleOf sets a multiple of value
|
||||||
|
func (p *Parameter) WithMultipleOf(number float64) *Parameter {
|
||||||
|
p.MultipleOf = &number
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaximum sets a maximum number value
|
||||||
|
func (p *Parameter) WithMaximum(max float64, exclusive bool) *Parameter {
|
||||||
|
p.Maximum = &max
|
||||||
|
p.ExclusiveMaximum = exclusive
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinimum sets a minimum number value
|
||||||
|
func (p *Parameter) WithMinimum(min float64, exclusive bool) *Parameter {
|
||||||
|
p.Minimum = &min
|
||||||
|
p.ExclusiveMinimum = exclusive
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnum sets a the enum values (replace)
|
||||||
|
func (p *Parameter) WithEnum(values ...interface{}) *Parameter {
|
||||||
|
p.Enum = append([]interface{}{}, values...)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMaxItems sets the max items
|
||||||
|
func (p *Parameter) WithMaxItems(size int64) *Parameter {
|
||||||
|
p.MaxItems = &size
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMinItems sets the min items
|
||||||
|
func (p *Parameter) WithMinItems(size int64) *Parameter {
|
||||||
|
p.MinItems = &size
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueValues dictates that this array can only have unique items
|
||||||
|
func (p *Parameter) UniqueValues() *Parameter {
|
||||||
|
p.UniqueItems = true
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowDuplicates this array can have duplicates
|
||||||
|
func (p *Parameter) AllowDuplicates() *Parameter {
|
||||||
|
p.UniqueItems = false
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValidations is a fluent method to set parameter validations
|
||||||
|
func (p *Parameter) WithValidations(val CommonValidations) *Parameter {
|
||||||
|
p.SetValidations(SchemaValidations{CommonValidations: val})
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||||
|
func (p *Parameter) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &p.CommonValidations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &p.SimpleSchema); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &p.ParamProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts this items object to JSON
|
||||||
|
func (p Parameter) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(p.CommonValidations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(p.SimpleSchema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b3, err := json.Marshal(p.Refable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b4, err := json.Marshal(p.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b5, err := json.Marshal(p.ParamProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return swag.ConcatJSON(b3, b1, b2, b4, b5), nil
|
||||||
|
}
|
||||||
87
vendor/github.com/go-openapi/spec/path_item.go
generated
vendored
Normal file
87
vendor/github.com/go-openapi/spec/path_item.go
generated
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonpointer"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathItemProps the path item specific properties
|
||||||
|
type PathItemProps struct {
|
||||||
|
Get *Operation `json:"get,omitempty"`
|
||||||
|
Put *Operation `json:"put,omitempty"`
|
||||||
|
Post *Operation `json:"post,omitempty"`
|
||||||
|
Delete *Operation `json:"delete,omitempty"`
|
||||||
|
Options *Operation `json:"options,omitempty"`
|
||||||
|
Head *Operation `json:"head,omitempty"`
|
||||||
|
Patch *Operation `json:"patch,omitempty"`
|
||||||
|
Parameters []Parameter `json:"parameters,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathItem describes the operations available on a single path.
|
||||||
|
// A Path Item may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
||||||
|
// The path itself is still exposed to the documentation viewer but they will
|
||||||
|
// not know which operations and parameters are available.
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#pathItemObject
|
||||||
|
type PathItem struct {
|
||||||
|
Refable
|
||||||
|
VendorExtensible
|
||||||
|
PathItemProps
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (p PathItem) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if ex, ok := p.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
if token == jsonRef {
|
||||||
|
return &p.Ref, nil
|
||||||
|
}
|
||||||
|
r, _, err := jsonpointer.GetForToken(p.PathItemProps, token)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||||
|
func (p *PathItem) UnmarshalJSON(data []byte) error {
|
||||||
|
if err := json.Unmarshal(data, &p.Refable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &p.VendorExtensible); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &p.PathItemProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts this items object to JSON
|
||||||
|
func (p PathItem) MarshalJSON() ([]byte, error) {
|
||||||
|
b3, err := json.Marshal(p.Refable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b4, err := json.Marshal(p.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b5, err := json.Marshal(p.PathItemProps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
concated := swag.ConcatJSON(b3, b4, b5)
|
||||||
|
return concated, nil
|
||||||
|
}
|
||||||
97
vendor/github.com/go-openapi/spec/paths.go
generated
vendored
Normal file
97
vendor/github.com/go-openapi/spec/paths.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Paths holds the relative paths to the individual endpoints.
|
||||||
|
// The path is appended to the [`basePath`](http://goo.gl/8us55a#swaggerBasePath) in order
|
||||||
|
// to construct the full URL.
|
||||||
|
// The Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
|
||||||
|
//
|
||||||
|
// For more information: http://goo.gl/8us55a#pathsObject
|
||||||
|
type Paths struct {
|
||||||
|
VendorExtensible
|
||||||
|
Paths map[string]PathItem `json:"-"` // custom serializer to flatten this, each entry must start with "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONLookup look up a value by the json property name
|
||||||
|
func (p Paths) JSONLookup(token string) (interface{}, error) {
|
||||||
|
if pi, ok := p.Paths[token]; ok {
|
||||||
|
return &pi, nil
|
||||||
|
}
|
||||||
|
if ex, ok := p.Extensions[token]; ok {
|
||||||
|
return &ex, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("object has no field %q", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON hydrates this items instance with the data from JSON
|
||||||
|
func (p *Paths) UnmarshalJSON(data []byte) error {
|
||||||
|
var res map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for k, v := range res {
|
||||||
|
if strings.HasPrefix(strings.ToLower(k), "x-") {
|
||||||
|
if p.Extensions == nil {
|
||||||
|
p.Extensions = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
var d interface{}
|
||||||
|
if err := json.Unmarshal(v, &d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Extensions[k] = d
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(k, "/") {
|
||||||
|
if p.Paths == nil {
|
||||||
|
p.Paths = make(map[string]PathItem)
|
||||||
|
}
|
||||||
|
var pi PathItem
|
||||||
|
if err := json.Unmarshal(v, &pi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Paths[k] = pi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts this items object to JSON
|
||||||
|
func (p Paths) MarshalJSON() ([]byte, error) {
|
||||||
|
b1, err := json.Marshal(p.VendorExtensible)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pths := make(map[string]PathItem)
|
||||||
|
for k, v := range p.Paths {
|
||||||
|
if strings.HasPrefix(k, "/") {
|
||||||
|
pths[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b2, err := json.Marshal(pths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
concated := swag.ConcatJSON(b1, b2)
|
||||||
|
return concated, nil
|
||||||
|
}
|
||||||
91
vendor/github.com/go-openapi/spec/properties.go
generated
vendored
Normal file
91
vendor/github.com/go-openapi/spec/properties.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrderSchemaItem holds a named schema (e.g. from a property of an object)
|
||||||
|
type OrderSchemaItem struct {
|
||||||
|
Name string
|
||||||
|
Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderSchemaItems is a sortable slice of named schemas.
|
||||||
|
// The ordering is defined by the x-order schema extension.
|
||||||
|
type OrderSchemaItems []OrderSchemaItem
|
||||||
|
|
||||||
|
// MarshalJSON produces a json object with keys defined by the name schemas
|
||||||
|
// of the OrderSchemaItems slice, keeping the original order of the slice.
|
||||||
|
func (items OrderSchemaItems) MarshalJSON() ([]byte, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.WriteString("{")
|
||||||
|
for i := range items {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
buf.WriteString("\"")
|
||||||
|
buf.WriteString(items[i].Name)
|
||||||
|
buf.WriteString("\":")
|
||||||
|
bs, err := json.Marshal(&items[i].Schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf.Write(bs)
|
||||||
|
}
|
||||||
|
buf.WriteString("}")
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (items OrderSchemaItems) Len() int { return len(items) }
|
||||||
|
func (items OrderSchemaItems) Swap(i, j int) { items[i], items[j] = items[j], items[i] }
|
||||||
|
func (items OrderSchemaItems) Less(i, j int) (ret bool) {
|
||||||
|
ii, oki := items[i].Extensions.GetString("x-order")
|
||||||
|
ij, okj := items[j].Extensions.GetString("x-order")
|
||||||
|
if oki {
|
||||||
|
if okj {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
defer func() {
|
||||||
|
if err = recover(); err != nil {
|
||||||
|
ret = items[i].Name < items[j].Name
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ret = reflect.ValueOf(ii).String() < reflect.ValueOf(ij).String()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return reflect.ValueOf(ii).Int() < reflect.ValueOf(ij).Int()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if okj {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return items[i].Name < items[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaProperties is a map representing the properties of a Schema object.
|
||||||
|
// It knows how to transform its keys into an ordered slice.
|
||||||
|
type SchemaProperties map[string]Schema
|
||||||
|
|
||||||
|
// ToOrderedSchemaItems transforms the map of properties into a sortable slice
|
||||||
|
func (properties SchemaProperties) ToOrderedSchemaItems() OrderSchemaItems {
|
||||||
|
items := make(OrderSchemaItems, 0, len(properties))
|
||||||
|
for k, v := range properties {
|
||||||
|
items = append(items, OrderSchemaItem{
|
||||||
|
Name: k,
|
||||||
|
Schema: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Sort(items)
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON produces properties as json, keeping their order.
|
||||||
|
func (properties SchemaProperties) MarshalJSON() ([]byte, error) {
|
||||||
|
if properties == nil {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return json.Marshal(properties.ToOrderedSchemaItems())
|
||||||
|
}
|
||||||
193
vendor/github.com/go-openapi/spec/ref.go
generated
vendored
Normal file
193
vendor/github.com/go-openapi/spec/ref.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2015 go-swagger maintainers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-openapi/jsonreference"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Refable is a struct for things that accept a $ref property
|
||||||
|
type Refable struct {
|
||||||
|
Ref Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the ref to json
|
||||||
|
func (r Refable) MarshalJSON() ([]byte, error) {
|
||||||
|
return r.Ref.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshalss the ref from json
|
||||||
|
func (r *Refable) UnmarshalJSON(d []byte) error {
|
||||||
|
return json.Unmarshal(d, &r.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref represents a json reference that is potentially resolved
|
||||||
|
type Ref struct {
|
||||||
|
jsonreference.Ref
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteURI gets the remote uri part of the ref
|
||||||
|
func (r *Ref) RemoteURI() string {
|
||||||
|
if r.String() == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
u := *r.GetURL()
|
||||||
|
u.Fragment = ""
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidURI returns true when the url the ref points to can be found
|
||||||
|
func (r *Ref) IsValidURI(basepaths ...string) bool {
|
||||||
|
if r.String() == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
v := r.RemoteURI()
|
||||||
|
if v == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.HasFullURL {
|
||||||
|
//nolint:noctx,gosec
|
||||||
|
rr, err := http.Get(v)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer rr.Body.Close()
|
||||||
|
|
||||||
|
return rr.StatusCode/100 == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(r.HasFileScheme || r.HasFullFilePath || r.HasURLPathOnly) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for local file
|
||||||
|
pth := v
|
||||||
|
if r.HasURLPathOnly {
|
||||||
|
base := "."
|
||||||
|
if len(basepaths) > 0 {
|
||||||
|
base = filepath.Dir(filepath.Join(basepaths...))
|
||||||
|
}
|
||||||
|
p, e := filepath.Abs(filepath.ToSlash(filepath.Join(base, pth)))
|
||||||
|
if e != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pth = p
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(filepath.ToSlash(pth))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inherits creates a new reference from a parent and a child
|
||||||
|
// If the child cannot inherit from the parent, an error is returned
|
||||||
|
func (r *Ref) Inherits(child Ref) (*Ref, error) {
|
||||||
|
ref, err := r.Ref.Inherits(child.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Ref{Ref: *ref}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRef creates a new instance of a ref object
|
||||||
|
// returns an error when the reference uri is an invalid uri
|
||||||
|
func NewRef(refURI string) (Ref, error) {
|
||||||
|
ref, err := jsonreference.New(refURI)
|
||||||
|
if err != nil {
|
||||||
|
return Ref{}, err
|
||||||
|
}
|
||||||
|
return Ref{Ref: ref}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCreateRef creates a ref object but panics when refURI is invalid.
|
||||||
|
// Use the NewRef method for a version that returns an error.
|
||||||
|
func MustCreateRef(refURI string) Ref {
|
||||||
|
return Ref{Ref: jsonreference.MustCreateRef(refURI)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals this ref into a JSON object
|
||||||
|
func (r Ref) MarshalJSON() ([]byte, error) {
|
||||||
|
str := r.String()
|
||||||
|
if str == "" {
|
||||||
|
if r.IsRoot() {
|
||||||
|
return []byte(`{"$ref":""}`), nil
|
||||||
|
}
|
||||||
|
return []byte("{}"), nil
|
||||||
|
}
|
||||||
|
v := map[string]interface{}{"$ref": str}
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals this ref from a JSON object
|
||||||
|
func (r *Ref) UnmarshalJSON(d []byte) error {
|
||||||
|
var v map[string]interface{}
|
||||||
|
if err := json.Unmarshal(d, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.fromMap(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode provides a safe gob encoder for Ref
|
||||||
|
func (r Ref) GobEncode() ([]byte, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
raw, err := r.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = gob.NewEncoder(&b).Encode(raw)
|
||||||
|
return b.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode provides a safe gob decoder for Ref
|
||||||
|
func (r *Ref) GobDecode(b []byte) error {
|
||||||
|
var raw []byte
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
err := gob.NewDecoder(buf).Decode(&raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(raw, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Ref) fromMap(v map[string]interface{}) error {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if vv, ok := v["$ref"]; ok {
|
||||||
|
if str, ok := vv.(string); ok {
|
||||||
|
ref, err := jsonreference.New(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*r = Ref{Ref: ref}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
127
vendor/github.com/go-openapi/spec/resolver.go
generated
vendored
Normal file
127
vendor/github.com/go-openapi/spec/resolver.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveAnyWithBase(root interface{}, ref *Ref, result interface{}, options *ExpandOptions) error {
|
||||||
|
options = optionsOrDefault(options)
|
||||||
|
resolver := defaultSchemaLoader(root, options, nil, nil)
|
||||||
|
|
||||||
|
if err := resolver.Resolve(ref, result, options.RelativeBase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveRefWithBase resolves a reference against a context root with preservation of base path
|
||||||
|
func ResolveRefWithBase(root interface{}, ref *Ref, options *ExpandOptions) (*Schema, error) {
|
||||||
|
result := new(Schema)
|
||||||
|
|
||||||
|
if err := resolveAnyWithBase(root, ref, result, options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveRef resolves a reference for a schema against a context root
|
||||||
|
// ref is guaranteed to be in root (no need to go to external files)
|
||||||
|
//
|
||||||
|
// ResolveRef is ONLY called from the code generation module
|
||||||
|
func ResolveRef(root interface{}, ref *Ref) (*Schema, error) {
|
||||||
|
res, _, err := ref.GetPointer().Get(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sch := res.(type) {
|
||||||
|
case Schema:
|
||||||
|
return &sch, nil
|
||||||
|
case *Schema:
|
||||||
|
return sch, nil
|
||||||
|
case map[string]interface{}:
|
||||||
|
newSch := new(Schema)
|
||||||
|
if err = swag.DynamicJSONToStruct(sch, newSch); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newSch, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("type: %T: %w", sch, ErrUnknownTypeForReference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveParameterWithBase resolves a parameter reference against a context root and base path
|
||||||
|
func ResolveParameterWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Parameter, error) {
|
||||||
|
result := new(Parameter)
|
||||||
|
|
||||||
|
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveParameter resolves a parameter reference against a context root
|
||||||
|
func ResolveParameter(root interface{}, ref Ref) (*Parameter, error) {
|
||||||
|
return ResolveParameterWithBase(root, ref, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveResponseWithBase resolves response a reference against a context root and base path
|
||||||
|
func ResolveResponseWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Response, error) {
|
||||||
|
result := new(Response)
|
||||||
|
|
||||||
|
err := resolveAnyWithBase(root, &ref, result, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveResponse resolves response a reference against a context root
|
||||||
|
func ResolveResponse(root interface{}, ref Ref) (*Response, error) {
|
||||||
|
return ResolveResponseWithBase(root, ref, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePathItemWithBase resolves response a path item against a context root and base path
|
||||||
|
func ResolvePathItemWithBase(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) {
|
||||||
|
result := new(PathItem)
|
||||||
|
|
||||||
|
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePathItem resolves response a path item against a context root and base path
|
||||||
|
//
|
||||||
|
// Deprecated: use ResolvePathItemWithBase instead
|
||||||
|
func ResolvePathItem(root interface{}, ref Ref, options *ExpandOptions) (*PathItem, error) {
|
||||||
|
return ResolvePathItemWithBase(root, ref, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveItemsWithBase resolves parameter items reference against a context root and base path.
|
||||||
|
//
|
||||||
|
// NOTE: stricly speaking, this construct is not supported by Swagger 2.0.
|
||||||
|
// Similarly, $ref are forbidden in response headers.
|
||||||
|
func ResolveItemsWithBase(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) {
|
||||||
|
result := new(Items)
|
||||||
|
|
||||||
|
if err := resolveAnyWithBase(root, &ref, result, options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveItems resolves parameter items reference against a context root and base path.
|
||||||
|
//
|
||||||
|
// Deprecated: use ResolveItemsWithBase instead
|
||||||
|
func ResolveItems(root interface{}, ref Ref, options *ExpandOptions) (*Items, error) {
|
||||||
|
return ResolveItemsWithBase(root, ref, options)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user