Add v16.8.0

This commit is contained in:
Jan Stabenow 2022-06-03 17:21:52 +02:00
parent c8bebd95ef
commit 9746248c10
No known key found for this signature in database
GPG key ID: 9C22DD65A9AAF133
431 changed files with 14782 additions and 13944 deletions

View file

@ -3,6 +3,8 @@
/data/** /data/**
/test/** /test/**
Dockerfile* Dockerfile*
CHANGELOG.md
README.md
*.md *.md
.github/* .github/*
.github_build/* .github_build/*

11
CHANGELOG.md Normal file
View file

@ -0,0 +1,11 @@
# Core
#### Core v16.7.2 > v16.8.0
- Add purge_on_delete function
- Mod updated dependencies
- Mod updated API docs
- Fix disabled session logging
- Fix FFmpeg skills reload
- Fix ignores processes with invalid references (thx Patron Ramakrishna Chillara)
- Fix code scanning alerts

View file

@ -1,28 +1,14 @@
ARG GOLANG_IMAGE=golang:1.18.2-alpine3.15 ARG CORE_IMAGE=datarhei/base:alpine-core-latest
ARG FFMPEG_IMAGE=datarhei/base:alpine-ffmpeg-3.15-4.4.2 ARG FFMPEG_IMAGE=datarhei/base:alpine-ffmpeg-latest
FROM $GOLANG_IMAGE as builder FROM $CORE_IMAGE as core
COPY . /dist/core
RUN apk add \
git \
make && \
cd /dist/core && \
go version && \
make release && \
make import
FROM $FFMPEG_IMAGE FROM $FFMPEG_IMAGE
COPY --from=builder /dist/core/core /core/bin/core COPY --from=core /core /core
COPY --from=builder /dist/core/import /core/bin/import
COPY --from=builder /dist/core/mime.types /core/mime.types
COPY --from=builder /dist/core/run.sh /core/bin/run.sh
RUN ffmpeg -buildconf && \ RUN ffmpeg -buildconf
mkdir /core/config /core/data
ENV CORE_CONFIGFILE=/core/config/config.json ENV CORE_CONFIGFILE=/core/config/config.json
ENV CORE_STORAGE_DISK_DIR=/core/data ENV CORE_STORAGE_DISK_DIR=/core/data

18
Jenkinsfile vendored
View file

@ -1,18 +0,0 @@
pipeline {
agent any
triggers {
cron('H 0 * * 6')
}
options {
buildDiscarder(logRotator(numToKeepStr: '5', artifactNumToKeepStr: '5'))
}
stages {
stage('Build AMD64') {
steps {
sh 'docker build -f Dockerfile -t docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64 .'
sh 'docker push docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64'
sh 'docker rmi docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64'
}
}
}
}

View file

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright [yyyy] [name of copyright owner] Copyright 2020 FOSS GmbH
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -8,7 +8,7 @@ all: build
## build: Build core (default) ## build: Build core (default)
build: build:
go build -o core$(OSARCH) CGO_ENABLED=0 GOOS=linux GOARCH=${OSARCH} go build -o core${OSARCH}
## swagger: Update swagger API documentation (requires github.com/swaggo/swag) ## swagger: Update swagger API documentation (requires github.com/swaggo/swag)
swagger: swagger:
@ -34,6 +34,7 @@ fmt:
update: update:
go get -u go get -u
@-$(MAKE) tidy @-$(MAKE) tidy
@-$(MAKE) vendor
## tidy: Tidy up go.mod ## tidy: Tidy up go.mod
tidy: tidy:
@ -53,7 +54,7 @@ lint:
## import: Build import binary ## import: Build import binary
import: import:
cd app/import && go build -o ../../import -ldflags="-s -w" cd app/import && CGO_ENABLED=0 GOOS=linux GOARCH=${OSARCH} go build -o ../../import -ldflags="-s -w"
## coverage: Generate code coverage analysis ## coverage: Generate code coverage analysis
coverage: coverage:
@ -66,13 +67,13 @@ commit: vet fmt lint test build
## release: Build a release binary of core ## release: Build a release binary of core
release: release:
go build -o core -ldflags="-s -w -X github.com/datarhei/core/app.Commit=$(COMMIT) -X github.com/datarhei/core/app.Branch=$(BRANCH) -X github.com/datarhei/core/app.Build=$(BUILD)" CGO_ENABLED=0 GOOS=linux GOARCH=${OSARCH} go build -o core -ldflags="-s -w -X github.com/datarhei/core/app.Commit=$(COMMIT) -X github.com/datarhei/core/app.Branch=$(BRANCH) -X github.com/datarhei/core/app.Build=$(BUILD)"
## docker: Build standard Docker image ## docker: Build standard Docker image
docker: docker:
docker build -t core:$(SHORTCOMMIT) . docker build -t core:$(SHORTCOMMIT) .
.PHONY: help build swagger test vet fmt vendor commit coverage lint release import .PHONY: help build swagger test vet fmt vendor commit coverage lint release import update
## help: Show all commands ## help: Show all commands
help: Makefile help: Makefile

View file

@ -1,6 +1,6 @@
# Core # Core
The cloud-native a/v media processor. The cloud-native audio/video processing API.
datarhei Core is management for FFmpeg processes without development effort. It is a central interface for mapping AV processes, is responsible for design and management, and provides all necessary interfaces to access the video content. The included control for FFmpeg can keep all used functions reliable and executable without the need for software developers to take care of it. In addition, process and resource limitation for all FFmpeg processes protects the host system from application overload. The overall system gives access to current process values (CPU, RAM) and complete control of system resources and loads with statistical access to process data and current and historical logs. datarhei Core is management for FFmpeg processes without development effort. It is a central interface for mapping AV processes, is responsible for design and management, and provides all necessary interfaces to access the video content. The included control for FFmpeg can keep all used functions reliable and executable without the need for software developers to take care of it. In addition, process and resource limitation for all FFmpeg processes protects the host system from application overload. The overall system gives access to current process values (CPU, RAM) and complete control of system resources and loads with statistical access to process data and current and historical logs.
@ -531,7 +531,8 @@ With the process API call, you can manage different FFmpeg processes. A process
"cleanup": [{ "cleanup": [{
"pattern": "(memfs|diskfs):...", "pattern": "(memfs|diskfs):...",
"max_files: "number, "max_files: "number,
"max_file_age_seconds": "number" "max_file_age_seconds": "number",
"purge_on_delete: "(true|false)"
}] }]
}, },
... list of outputs ... ... list of outputs ...
@ -556,7 +557,8 @@ permitted maximum age for the files matching that pattern. The pattern starts wi
which filesystem this rule is designated to. Then a [glob pattern](https://pkg.go.dev/path/filepath#Match) follows to which filesystem this rule is designated to. Then a [glob pattern](https://pkg.go.dev/path/filepath#Match) follows to
identify the files. If `max_files` is set to a number > 0, then the oldest files from the matching files will be deleted if identify the files. If `max_files` is set to a number > 0, then the oldest files from the matching files will be deleted if
the list of matching files is longer than that number. If `max_file_age_seconds` is set to a number > 0, then all files the list of matching files is longer than that number. If `max_file_age_seconds` is set to a number > 0, then all files
that are older than this number of seconds from the matching files will be deleted. that are older than this number of seconds from the matching files will be deleted. If `purge_on_delete` is set to `true`,
then all matching files will be deleted when the process is deleted.
The API calls are The API calls are
@ -975,5 +977,4 @@ Before committing changes, you should run `make commit` to ensure that the sourc
## License ## License
See the [LICENSE](./LICENSE) file for licensing information. datarhei/core is licensed under the Apache License 2.0

View file

@ -29,8 +29,8 @@ func (v versionInfo) MinorString() string {
// Version of the app // Version of the app
var Version = versionInfo{ var Version = versionInfo{
Major: 16, Major: 16,
Minor: 7, Minor: 8,
Patch: 2, Patch: 0,
} }
// Commit is the git commit the app is build from. It should be filled in during compilation // Commit is the git commit the app is build from. It should be filled in during compilation

View file

@ -1,32 +1,23 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag
// 2022-03-31 16:22:16.716974 +0200 CEST m=+0.265947626
package docs package docs
import ( import "github.com/swaggo/swag"
"bytes"
"encoding/json"
"strings"
"github.com/alecthomas/template" const docTemplate = `{
"github.com/swaggo/swag"
)
var doc = `{
"schemes": {{ marshal .Schemes }}, "schemes": {{ marshal .Schemes }},
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "{{.Description}}", "description": "{{escape .Description}}",
"title": "{{.Title}}", "title": "{{.Title}}",
"contact": { "contact": {
"name": "datarheiCORE Support", "name": "datarhei Core Support",
"url": "https://www.datarhei.com", "url": "https://www.datarhei.com",
"email": "hello@datarhei.com" "email": "hello@datarhei.com"
}, },
"license": { "license": {
"name": "???", "name": "Apache 2.0",
"url": "nothing" "url": "https://github.com/datarhei/core/blob/main/LICENSE"
}, },
"version": "{{.Version}}" "version": "{{.Version}}"
}, },
@ -70,7 +61,9 @@ var doc = `{
"summary": "Load GraphQL playground", "summary": "Load GraphQL playground",
"operationId": "graph-playground", "operationId": "graph-playground",
"responses": { "responses": {
"200": {} "200": {
"description": ""
}
} }
} }
}, },
@ -787,9 +780,7 @@ var doc = `{
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -830,17 +821,13 @@ var doc = `{
"name": "data", "name": "data",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1247,9 +1234,7 @@ var doc = `{
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1297,17 +1282,13 @@ var doc = `{
"name": "data", "name": "data",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1849,12 +1830,6 @@ var doc = `{
"schema": { "schema": {
"$ref": "#/definitions/api.SessionsSummary" "$ref": "#/definitions/api.SessionsSummary"
} }
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
} }
} }
} }
@ -1886,12 +1861,6 @@ var doc = `{
"schema": { "schema": {
"$ref": "#/definitions/api.SessionsActive" "$ref": "#/definitions/api.SessionsActive"
} }
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
} }
} }
} }
@ -2233,14 +2202,12 @@ var doc = `{
"type": "string" "type": "string"
}, },
"input": { "input": {
"type": "object",
"$ref": "#/definitions/api.AVstreamIO" "$ref": "#/definitions/api.AVstreamIO"
}, },
"looping": { "looping": {
"type": "boolean" "type": "boolean"
}, },
"output": { "output": {
"type": "object",
"$ref": "#/definitions/api.AVstreamIO" "$ref": "#/definitions/api.AVstreamIO"
}, },
"queue": { "queue": {
@ -2294,7 +2261,6 @@ var doc = `{
"type": "integer" "type": "integer"
}, },
"version": { "version": {
"type": "object",
"$ref": "#/definitions/api.Version" "$ref": "#/definitions/api.Version"
} }
} }
@ -2319,9 +2285,324 @@ var doc = `{
"api.Config": { "api.Config": {
"type": "object", "type": "object",
"properties": { "properties": {
"client": { "config": {
"$ref": "#/definitions/api.ConfigData"
},
"created_at": {
"type": "string" "type": "string"
}, },
"loaded_at": {
"type": "string"
},
"overrides": {
"type": "array",
"items": {
"type": "string"
}
},
"updated_at": {
"type": "string"
}
}
},
"api.ConfigData": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"api": {
"type": "object",
"properties": {
"access": {
"type": "object",
"properties": {
"http": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"https": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"auth": {
"type": "object",
"properties": {
"auth0": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"tenants": {
"type": "array",
"items": {
"$ref": "#/definitions/config.Auth0Tenant"
}
}
}
},
"disable_localhost": {
"type": "boolean"
},
"enable": {
"type": "boolean"
},
"jwt": {
"type": "object",
"properties": {
"secret": {
"type": "string"
}
}
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"read_only": {
"type": "boolean"
}
}
},
"created_at": {
"type": "string"
},
"db": {
"type": "object",
"properties": {
"dir": {
"type": "string"
}
}
},
"debug": {
"type": "object",
"properties": {
"force_gc": {
"type": "integer"
},
"profiling": {
"type": "boolean"
}
}
},
"ffmpeg": {
"type": "object",
"properties": {
"access": {
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"output": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"binary": {
"type": "string"
},
"log": {
"type": "object",
"properties": {
"max_history": {
"type": "integer"
},
"max_lines": {
"type": "integer"
}
}
},
"max_processes": {
"type": "integer"
}
}
},
"host": {
"type": "object",
"properties": {
"auto": {
"type": "boolean"
},
"name": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "string"
},
"log": {
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": [
"debug",
"info",
"warn",
"error",
"silent"
]
},
"max_lines": {
"type": "integer"
},
"topics": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"metrics": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"enable_prometheus": {
"type": "boolean"
},
"interval_sec": {
"description": "seconds",
"type": "integer"
},
"range_sec": {
"description": "seconds",
"type": "integer"
}
}
},
"name": {
"type": "string"
},
"playout": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"max_port": {
"type": "integer"
},
"min_port": {
"type": "integer"
}
}
},
"router": {
"type": "object",
"properties": {
"blocked_prefixes": {
"type": "array",
"items": {
"type": "string"
}
},
"routes": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"ui_path": {
"type": "string"
}
}
},
"rtmp": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"app": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"enable_tls": {
"type": "boolean"
},
"token": {
"type": "string"
}
}
},
"service": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"token": { "token": {
"type": "string" "type": "string"
}, },
@ -2330,6 +2611,141 @@ var doc = `{
} }
} }
}, },
"sessions": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"ip_ignorelist": {
"type": "array",
"items": {
"type": "string"
}
},
"max_bitrate_mbit": {
"type": "integer"
},
"max_sessions": {
"type": "integer"
},
"persist": {
"type": "boolean"
},
"persist_interval_sec": {
"type": "integer"
},
"session_timeout_sec": {
"type": "integer"
}
}
},
"storage": {
"type": "object",
"properties": {
"cors": {
"type": "object",
"properties": {
"origins": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"disk": {
"type": "object",
"properties": {
"cache": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"max_file_size_mbytes": {
"type": "integer"
},
"max_size_mbytes": {
"type": "integer"
},
"ttl_seconds": {
"type": "integer"
},
"types": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"dir": {
"type": "string"
},
"max_size_mbytes": {
"type": "integer"
}
}
},
"memory": {
"type": "object",
"properties": {
"auth": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"max_size_mbytes": {
"type": "integer"
},
"purge": {
"type": "boolean"
}
}
},
"mimetypes_file": {
"type": "string"
}
}
},
"tls": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"auto": {
"type": "boolean"
},
"cert_file": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"key_file": {
"type": "string"
}
}
},
"update_check": {
"type": "boolean"
},
"version": {
"type": "integer"
}
}
},
"api.ConfigError": { "api.ConfigError": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
@ -2376,22 +2792,16 @@ var doc = `{
"query": { "query": {
"type": "string" "type": "string"
}, },
"variables": { "variables": {}
"type": "object"
}
} }
}, },
"api.GraphResponse": { "api.GraphResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {},
"type": "object"
},
"errors": { "errors": {
"type": "array", "type": "array",
"items": { "items": {}
"type": "object"
}
} }
} }
}, },
@ -2433,9 +2843,6 @@ var doc = `{
} }
} }
}, },
"api.Metadata": {
"type": "object"
},
"api.MetricsQuery": { "api.MetricsQuery": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2448,7 +2855,7 @@ var doc = `{
"$ref": "#/definitions/api.MetricsQueryMetric" "$ref": "#/definitions/api.MetricsQueryMetric"
} }
}, },
"timeframe_sec": { "timerange_sec": {
"type": "integer" "type": "integer"
} }
} }
@ -2470,11 +2877,17 @@ var doc = `{
"api.MetricsResponse": { "api.MetricsResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"interval_sec": {
"type": "integer"
},
"metrics": { "metrics": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/api.MetricsResponseMetric" "$ref": "#/definitions/api.MetricsResponseMetric"
} }
},
"timerange_sec": {
"type": "integer"
} }
} }
}, },
@ -2493,13 +2906,21 @@ var doc = `{
"values": { "values": {
"type": "array", "type": "array",
"items": { "items": {
"type": "array", "$ref": "#/definitions/api.MetricsResponseValue"
"items": { }
}
}
},
"api.MetricsResponseValue": {
"type": "object",
"properties": {
"ts": {
"type": "string"
},
"value": {
"type": "number" "type": "number"
} }
} }
}
}
}, },
"api.PlayoutStatus": { "api.PlayoutStatus": {
"type": "object", "type": "object",
@ -2507,9 +2928,7 @@ var doc = `{
"aqueue": { "aqueue": {
"type": "integer" "type": "integer"
}, },
"debug": { "debug": {},
"type": "object"
},
"drop": { "drop": {
"type": "integer" "type": "integer"
}, },
@ -2529,14 +2948,12 @@ var doc = `{
"type": "string" "type": "string"
}, },
"input": { "input": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusIO" "$ref": "#/definitions/api.PlayoutStatusIO"
}, },
"looping": { "looping": {
"type": "boolean" "type": "boolean"
}, },
"output": { "output": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusIO" "$ref": "#/definitions/api.PlayoutStatusIO"
}, },
"queue": { "queue": {
@ -2546,7 +2963,6 @@ var doc = `{
"type": "integer" "type": "integer"
}, },
"swap": { "swap": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusSwap" "$ref": "#/definitions/api.PlayoutStatusSwap"
}, },
"url": { "url": {
@ -2672,7 +3088,6 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"config": { "config": {
"type": "object",
"$ref": "#/definitions/api.ProcessConfig" "$ref": "#/definitions/api.ProcessConfig"
}, },
"created_at": { "created_at": {
@ -2681,19 +3096,14 @@ var doc = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"metadata": { "metadata": {},
"type": "object",
"$ref": "#/definitions/api.Metadata"
},
"reference": { "reference": {
"type": "string" "type": "string"
}, },
"report": { "report": {
"type": "object",
"$ref": "#/definitions/api.ProcessReport" "$ref": "#/definitions/api.ProcessReport"
}, },
"state": { "state": {
"type": "object",
"$ref": "#/definitions/api.ProcessState" "$ref": "#/definitions/api.ProcessState"
}, },
"type": { "type": {
@ -2704,10 +3114,8 @@ var doc = `{
"api.ProcessConfig": { "api.ProcessConfig": {
"type": "object", "type": "object",
"required": [ "required": [
"id",
"input", "input",
"output", "output"
"type"
], ],
"properties": { "properties": {
"autostart": { "autostart": {
@ -2723,7 +3131,6 @@ var doc = `{
} }
}, },
"limits": { "limits": {
"type": "object",
"$ref": "#/definitions/api.ProcessConfigLimits" "$ref": "#/definitions/api.ProcessConfigLimits"
}, },
"options": { "options": {
@ -2753,7 +3160,8 @@ var doc = `{
"type": { "type": {
"type": "string", "type": "string",
"enum": [ "enum": [
"ffmpeg" "ffmpeg",
""
] ]
} }
} }
@ -2761,13 +3169,18 @@ var doc = `{
"api.ProcessConfigIO": { "api.ProcessConfigIO": {
"type": "object", "type": "object",
"required": [ "required": [
"address", "address"
"id"
], ],
"properties": { "properties": {
"address": { "address": {
"type": "string" "type": "string"
}, },
"cleanup": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ProcessConfigIOCleanup"
}
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -2779,6 +3192,26 @@ var doc = `{
} }
} }
}, },
"api.ProcessConfigIOCleanup": {
"type": "object",
"required": [
"pattern"
],
"properties": {
"max_file_age_seconds": {
"type": "integer"
},
"max_files": {
"type": "integer"
},
"pattern": {
"type": "string"
},
"purge_on_delete": {
"type": "boolean"
}
}
},
"api.ProcessConfigLimits": { "api.ProcessConfigLimits": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2870,7 +3303,6 @@ var doc = `{
"type": "string" "type": "string"
}, },
"progress": { "progress": {
"type": "object",
"$ref": "#/definitions/api.Progress" "$ref": "#/definitions/api.Progress"
}, },
"reconnect_seconds": { "reconnect_seconds": {
@ -2938,7 +3370,6 @@ var doc = `{
}, },
"avstream": { "avstream": {
"description": "avstream", "description": "avstream",
"type": "object",
"$ref": "#/definitions/api.AVstream" "$ref": "#/definitions/api.AVstream"
}, },
"bitrate_kbit": { "bitrate_kbit": {
@ -3091,11 +3522,9 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"active": { "active": {
"type": "object",
"$ref": "#/definitions/api.SessionSummaryActive" "$ref": "#/definitions/api.SessionSummaryActive"
}, },
"summary": { "summary": {
"type": "object",
"$ref": "#/definitions/api.SessionSummarySummary" "$ref": "#/definitions/api.SessionSummarySummary"
} }
} }
@ -3406,9 +3835,11 @@ var doc = `{
"type": "boolean" "type": "boolean"
}, },
"interval_sec": { "interval_sec": {
"description": "seconds",
"type": "integer" "type": "integer"
}, },
"range_sec": { "range_sec": {
"description": "seconds",
"type": "integer" "type": "integer"
} }
} }
@ -3444,6 +3875,9 @@ var doc = `{
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"ui_path": {
"type": "string"
} }
} }
}, },
@ -3481,7 +3915,7 @@ var doc = `{
} }
} }
}, },
"stats": { "sessions": {
"type": "object", "type": "object",
"properties": { "properties": {
"enable": { "enable": {
@ -3608,6 +4042,9 @@ var doc = `{
} }
} }
}, },
"update_check": {
"type": "boolean"
},
"version": { "version": {
"type": "integer" "type": "integer"
} }
@ -3919,49 +4356,18 @@ var doc = `{
} }
}` }`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{ var SwaggerInfo = &swag.Spec{
Version: "6.0", Version: "3.0",
Host: "", Host: "",
BasePath: "/", BasePath: "/",
Schemes: []string{}, Schemes: []string{},
Title: "datarhei Core API", Title: "datarhei Core API",
Description: "Expose REST API for the datarheiCORE", Description: "Expose REST API for the datarhei Core",
} InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
} }
func init() { func init() {
swag.Register(swag.Name, &s{}) swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
} }

View file

@ -4,15 +4,15 @@
"description": "Expose REST API for the datarhei Core", "description": "Expose REST API for the datarhei Core",
"title": "datarhei Core API", "title": "datarhei Core API",
"contact": { "contact": {
"name": "datarhei.com", "name": "datarhei Core Support",
"url": "https://www.datarhei.com", "url": "https://www.datarhei.com",
"email": "support@datarhei.com" "email": "hello@datarhei.com"
}, },
"license": { "license": {
"name": "Apache License Version 2.0", "name": "Apache 2.0",
"url": "https://github.com/datarhei/core/blob/main/LICENSE" "url": "https://github.com/datarhei/core/blob/main/LICENSE"
}, },
"version": "6.0" "version": "3.0"
}, },
"basePath": "/", "basePath": "/",
"paths": { "paths": {
@ -53,7 +53,9 @@
"summary": "Load GraphQL playground", "summary": "Load GraphQL playground",
"operationId": "graph-playground", "operationId": "graph-playground",
"responses": { "responses": {
"200": {} "200": {
"description": ""
}
} }
} }
}, },
@ -770,9 +772,7 @@
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -813,17 +813,13 @@
"name": "data", "name": "data",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1230,9 +1226,7 @@
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1280,17 +1274,13 @@
"name": "data", "name": "data",
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {}
"$ref": "#/definitions/api.Metadata"
}
}, },
"400": { "400": {
"description": "Bad Request", "description": "Bad Request",
@ -1832,12 +1822,6 @@
"schema": { "schema": {
"$ref": "#/definitions/api.SessionsSummary" "$ref": "#/definitions/api.SessionsSummary"
} }
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
} }
} }
} }
@ -1869,12 +1853,6 @@
"schema": { "schema": {
"$ref": "#/definitions/api.SessionsActive" "$ref": "#/definitions/api.SessionsActive"
} }
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
} }
} }
} }
@ -2216,14 +2194,12 @@
"type": "string" "type": "string"
}, },
"input": { "input": {
"type": "object",
"$ref": "#/definitions/api.AVstreamIO" "$ref": "#/definitions/api.AVstreamIO"
}, },
"looping": { "looping": {
"type": "boolean" "type": "boolean"
}, },
"output": { "output": {
"type": "object",
"$ref": "#/definitions/api.AVstreamIO" "$ref": "#/definitions/api.AVstreamIO"
}, },
"queue": { "queue": {
@ -2277,7 +2253,6 @@
"type": "integer" "type": "integer"
}, },
"version": { "version": {
"type": "object",
"$ref": "#/definitions/api.Version" "$ref": "#/definitions/api.Version"
} }
} }
@ -2302,9 +2277,324 @@
"api.Config": { "api.Config": {
"type": "object", "type": "object",
"properties": { "properties": {
"client": { "config": {
"$ref": "#/definitions/api.ConfigData"
},
"created_at": {
"type": "string" "type": "string"
}, },
"loaded_at": {
"type": "string"
},
"overrides": {
"type": "array",
"items": {
"type": "string"
}
},
"updated_at": {
"type": "string"
}
}
},
"api.ConfigData": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"api": {
"type": "object",
"properties": {
"access": {
"type": "object",
"properties": {
"http": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"https": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"auth": {
"type": "object",
"properties": {
"auth0": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"tenants": {
"type": "array",
"items": {
"$ref": "#/definitions/config.Auth0Tenant"
}
}
}
},
"disable_localhost": {
"type": "boolean"
},
"enable": {
"type": "boolean"
},
"jwt": {
"type": "object",
"properties": {
"secret": {
"type": "string"
}
}
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"read_only": {
"type": "boolean"
}
}
},
"created_at": {
"type": "string"
},
"db": {
"type": "object",
"properties": {
"dir": {
"type": "string"
}
}
},
"debug": {
"type": "object",
"properties": {
"force_gc": {
"type": "integer"
},
"profiling": {
"type": "boolean"
}
}
},
"ffmpeg": {
"type": "object",
"properties": {
"access": {
"type": "object",
"properties": {
"input": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"output": {
"type": "object",
"properties": {
"allow": {
"type": "array",
"items": {
"type": "string"
}
},
"block": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"binary": {
"type": "string"
},
"log": {
"type": "object",
"properties": {
"max_history": {
"type": "integer"
},
"max_lines": {
"type": "integer"
}
}
},
"max_processes": {
"type": "integer"
}
}
},
"host": {
"type": "object",
"properties": {
"auto": {
"type": "boolean"
},
"name": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "string"
},
"log": {
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": [
"debug",
"info",
"warn",
"error",
"silent"
]
},
"max_lines": {
"type": "integer"
},
"topics": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"metrics": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"enable_prometheus": {
"type": "boolean"
},
"interval_sec": {
"description": "seconds",
"type": "integer"
},
"range_sec": {
"description": "seconds",
"type": "integer"
}
}
},
"name": {
"type": "string"
},
"playout": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"max_port": {
"type": "integer"
},
"min_port": {
"type": "integer"
}
}
},
"router": {
"type": "object",
"properties": {
"blocked_prefixes": {
"type": "array",
"items": {
"type": "string"
}
},
"routes": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"ui_path": {
"type": "string"
}
}
},
"rtmp": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"app": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"enable_tls": {
"type": "boolean"
},
"token": {
"type": "string"
}
}
},
"service": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"token": { "token": {
"type": "string" "type": "string"
}, },
@ -2313,6 +2603,141 @@
} }
} }
}, },
"sessions": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"ip_ignorelist": {
"type": "array",
"items": {
"type": "string"
}
},
"max_bitrate_mbit": {
"type": "integer"
},
"max_sessions": {
"type": "integer"
},
"persist": {
"type": "boolean"
},
"persist_interval_sec": {
"type": "integer"
},
"session_timeout_sec": {
"type": "integer"
}
}
},
"storage": {
"type": "object",
"properties": {
"cors": {
"type": "object",
"properties": {
"origins": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"disk": {
"type": "object",
"properties": {
"cache": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"max_file_size_mbytes": {
"type": "integer"
},
"max_size_mbytes": {
"type": "integer"
},
"ttl_seconds": {
"type": "integer"
},
"types": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"dir": {
"type": "string"
},
"max_size_mbytes": {
"type": "integer"
}
}
},
"memory": {
"type": "object",
"properties": {
"auth": {
"type": "object",
"properties": {
"enable": {
"type": "boolean"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"max_size_mbytes": {
"type": "integer"
},
"purge": {
"type": "boolean"
}
}
},
"mimetypes_file": {
"type": "string"
}
}
},
"tls": {
"type": "object",
"properties": {
"address": {
"type": "string"
},
"auto": {
"type": "boolean"
},
"cert_file": {
"type": "string"
},
"enable": {
"type": "boolean"
},
"key_file": {
"type": "string"
}
}
},
"update_check": {
"type": "boolean"
},
"version": {
"type": "integer"
}
}
},
"api.ConfigError": { "api.ConfigError": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
@ -2359,22 +2784,16 @@
"query": { "query": {
"type": "string" "type": "string"
}, },
"variables": { "variables": {}
"type": "object"
}
} }
}, },
"api.GraphResponse": { "api.GraphResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {},
"type": "object"
},
"errors": { "errors": {
"type": "array", "type": "array",
"items": { "items": {}
"type": "object"
}
} }
} }
}, },
@ -2416,9 +2835,6 @@
} }
} }
}, },
"api.Metadata": {
"type": "object"
},
"api.MetricsQuery": { "api.MetricsQuery": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2431,7 +2847,7 @@
"$ref": "#/definitions/api.MetricsQueryMetric" "$ref": "#/definitions/api.MetricsQueryMetric"
} }
}, },
"timeframe_sec": { "timerange_sec": {
"type": "integer" "type": "integer"
} }
} }
@ -2453,11 +2869,17 @@
"api.MetricsResponse": { "api.MetricsResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"interval_sec": {
"type": "integer"
},
"metrics": { "metrics": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/api.MetricsResponseMetric" "$ref": "#/definitions/api.MetricsResponseMetric"
} }
},
"timerange_sec": {
"type": "integer"
} }
} }
}, },
@ -2476,13 +2898,21 @@
"values": { "values": {
"type": "array", "type": "array",
"items": { "items": {
"type": "array", "$ref": "#/definitions/api.MetricsResponseValue"
"items": { }
}
}
},
"api.MetricsResponseValue": {
"type": "object",
"properties": {
"ts": {
"type": "string"
},
"value": {
"type": "number" "type": "number"
} }
} }
}
}
}, },
"api.PlayoutStatus": { "api.PlayoutStatus": {
"type": "object", "type": "object",
@ -2490,9 +2920,7 @@
"aqueue": { "aqueue": {
"type": "integer" "type": "integer"
}, },
"debug": { "debug": {},
"type": "object"
},
"drop": { "drop": {
"type": "integer" "type": "integer"
}, },
@ -2512,14 +2940,12 @@
"type": "string" "type": "string"
}, },
"input": { "input": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusIO" "$ref": "#/definitions/api.PlayoutStatusIO"
}, },
"looping": { "looping": {
"type": "boolean" "type": "boolean"
}, },
"output": { "output": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusIO" "$ref": "#/definitions/api.PlayoutStatusIO"
}, },
"queue": { "queue": {
@ -2529,7 +2955,6 @@
"type": "integer" "type": "integer"
}, },
"swap": { "swap": {
"type": "object",
"$ref": "#/definitions/api.PlayoutStatusSwap" "$ref": "#/definitions/api.PlayoutStatusSwap"
}, },
"url": { "url": {
@ -2655,7 +3080,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"config": { "config": {
"type": "object",
"$ref": "#/definitions/api.ProcessConfig" "$ref": "#/definitions/api.ProcessConfig"
}, },
"created_at": { "created_at": {
@ -2664,19 +3088,14 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"metadata": { "metadata": {},
"type": "object",
"$ref": "#/definitions/api.Metadata"
},
"reference": { "reference": {
"type": "string" "type": "string"
}, },
"report": { "report": {
"type": "object",
"$ref": "#/definitions/api.ProcessReport" "$ref": "#/definitions/api.ProcessReport"
}, },
"state": { "state": {
"type": "object",
"$ref": "#/definitions/api.ProcessState" "$ref": "#/definitions/api.ProcessState"
}, },
"type": { "type": {
@ -2687,10 +3106,8 @@
"api.ProcessConfig": { "api.ProcessConfig": {
"type": "object", "type": "object",
"required": [ "required": [
"id",
"input", "input",
"output", "output"
"type"
], ],
"properties": { "properties": {
"autostart": { "autostart": {
@ -2706,7 +3123,6 @@
} }
}, },
"limits": { "limits": {
"type": "object",
"$ref": "#/definitions/api.ProcessConfigLimits" "$ref": "#/definitions/api.ProcessConfigLimits"
}, },
"options": { "options": {
@ -2736,7 +3152,8 @@
"type": { "type": {
"type": "string", "type": "string",
"enum": [ "enum": [
"ffmpeg" "ffmpeg",
""
] ]
} }
} }
@ -2744,13 +3161,18 @@
"api.ProcessConfigIO": { "api.ProcessConfigIO": {
"type": "object", "type": "object",
"required": [ "required": [
"address", "address"
"id"
], ],
"properties": { "properties": {
"address": { "address": {
"type": "string" "type": "string"
}, },
"cleanup": {
"type": "array",
"items": {
"$ref": "#/definitions/api.ProcessConfigIOCleanup"
}
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -2762,6 +3184,26 @@
} }
} }
}, },
"api.ProcessConfigIOCleanup": {
"type": "object",
"required": [
"pattern"
],
"properties": {
"max_file_age_seconds": {
"type": "integer"
},
"max_files": {
"type": "integer"
},
"pattern": {
"type": "string"
},
"purge_on_delete": {
"type": "boolean"
}
}
},
"api.ProcessConfigLimits": { "api.ProcessConfigLimits": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -2853,7 +3295,6 @@
"type": "string" "type": "string"
}, },
"progress": { "progress": {
"type": "object",
"$ref": "#/definitions/api.Progress" "$ref": "#/definitions/api.Progress"
}, },
"reconnect_seconds": { "reconnect_seconds": {
@ -2921,7 +3362,6 @@
}, },
"avstream": { "avstream": {
"description": "avstream", "description": "avstream",
"type": "object",
"$ref": "#/definitions/api.AVstream" "$ref": "#/definitions/api.AVstream"
}, },
"bitrate_kbit": { "bitrate_kbit": {
@ -3074,11 +3514,9 @@
"type": "object", "type": "object",
"properties": { "properties": {
"active": { "active": {
"type": "object",
"$ref": "#/definitions/api.SessionSummaryActive" "$ref": "#/definitions/api.SessionSummaryActive"
}, },
"summary": { "summary": {
"type": "object",
"$ref": "#/definitions/api.SessionSummarySummary" "$ref": "#/definitions/api.SessionSummarySummary"
} }
} }
@ -3389,9 +3827,11 @@
"type": "boolean" "type": "boolean"
}, },
"interval_sec": { "interval_sec": {
"description": "seconds",
"type": "integer" "type": "integer"
}, },
"range_sec": { "range_sec": {
"description": "seconds",
"type": "integer" "type": "integer"
} }
} }
@ -3427,6 +3867,9 @@
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"ui_path": {
"type": "string"
} }
} }
}, },
@ -3464,7 +3907,7 @@
} }
} }
}, },
"stats": { "sessions": {
"type": "object", "type": "object",
"properties": { "properties": {
"enable": { "enable": {
@ -3591,6 +4034,9 @@
} }
} }
}, },
"update_check": {
"type": "boolean"
},
"version": { "version": {
"type": "integer" "type": "integer"
} }

View file

@ -16,12 +16,10 @@ definitions:
type: string type: string
input: input:
$ref: '#/definitions/api.AVstreamIO' $ref: '#/definitions/api.AVstreamIO'
type: object
looping: looping:
type: boolean type: boolean
output: output:
$ref: '#/definitions/api.AVstreamIO' $ref: '#/definitions/api.AVstreamIO'
type: object
queue: queue:
type: integer type: integer
type: object type: object
@ -58,7 +56,6 @@ definitions:
version: version:
$ref: '#/definitions/api.Version' $ref: '#/definitions/api.Version'
type: object type: object
type: object
api.Command: api.Command:
properties: properties:
command: command:
@ -73,13 +70,305 @@ definitions:
type: object type: object
api.Config: api.Config:
properties: properties:
client: config:
$ref: '#/definitions/api.ConfigData'
created_at:
type: string type: string
loaded_at:
type: string
overrides:
items:
type: string
type: array
updated_at:
type: string
type: object
api.ConfigData:
properties:
address:
type: string
api:
properties:
access:
properties:
http:
properties:
allow:
items:
type: string
type: array
block:
items:
type: string
type: array
type: object
https:
properties:
allow:
items:
type: string
type: array
block:
items:
type: string
type: array
type: object
type: object
auth:
properties:
auth0:
properties:
enable:
type: boolean
tenants:
items:
$ref: '#/definitions/config.Auth0Tenant'
type: array
type: object
disable_localhost:
type: boolean
enable:
type: boolean
jwt:
properties:
secret:
type: string
type: object
password:
type: string
username:
type: string
type: object
read_only:
type: boolean
type: object
created_at:
type: string
db:
properties:
dir:
type: string
type: object
debug:
properties:
force_gc:
type: integer
profiling:
type: boolean
type: object
ffmpeg:
properties:
access:
properties:
input:
properties:
allow:
items:
type: string
type: array
block:
items:
type: string
type: array
type: object
output:
properties:
allow:
items:
type: string
type: array
block:
items:
type: string
type: array
type: object
type: object
binary:
type: string
log:
properties:
max_history:
type: integer
max_lines:
type: integer
type: object
max_processes:
type: integer
type: object
host:
properties:
auto:
type: boolean
name:
items:
type: string
type: array
type: object
id:
type: string
log:
properties:
level:
enum:
- debug
- info
- warn
- error
- silent
type: string
max_lines:
type: integer
topics:
items:
type: string
type: array
type: object
metrics:
properties:
enable:
type: boolean
enable_prometheus:
type: boolean
interval_sec:
description: seconds
type: integer
range_sec:
description: seconds
type: integer
type: object
name:
type: string
playout:
properties:
enable:
type: boolean
max_port:
type: integer
min_port:
type: integer
type: object
router:
properties:
blocked_prefixes:
items:
type: string
type: array
routes:
additionalProperties:
type: string
type: object
ui_path:
type: string
type: object
rtmp:
properties:
address:
type: string
app:
type: string
enable:
type: boolean
enable_tls:
type: boolean
token:
type: string
type: object
service:
properties:
enable:
type: boolean
token: token:
type: string type: string
url: url:
type: string type: string
type: object type: object
sessions:
properties:
enable:
type: boolean
ip_ignorelist:
items:
type: string
type: array
max_bitrate_mbit:
type: integer
max_sessions:
type: integer
persist:
type: boolean
persist_interval_sec:
type: integer
session_timeout_sec:
type: integer
type: object
storage:
properties:
cors:
properties:
origins:
items:
type: string
type: array
type: object
disk:
properties:
cache:
properties:
enable:
type: boolean
max_file_size_mbytes:
type: integer
max_size_mbytes:
type: integer
ttl_seconds:
type: integer
types:
items:
type: string
type: array
type: object
dir:
type: string
max_size_mbytes:
type: integer
type: object
memory:
properties:
auth:
properties:
enable:
type: boolean
password:
type: string
username:
type: string
type: object
max_size_mbytes:
type: integer
purge:
type: boolean
type: object
mimetypes_file:
type: string
type: object
tls:
properties:
address:
type: string
auto:
type: boolean
cert_file:
type: string
enable:
type: boolean
key_file:
type: string
type: object
update_check:
type: boolean
version:
type: integer
type: object
api.ConfigError: api.ConfigError:
additionalProperties: additionalProperties:
items: items:
@ -110,16 +399,13 @@ definitions:
properties: properties:
query: query:
type: string type: string
variables: variables: {}
type: object
type: object type: object
api.GraphResponse: api.GraphResponse:
properties: properties:
data: data: {}
type: object
errors: errors:
items: items: {}
type: object
type: array type: array
type: object type: object
api.JWT: api.JWT:
@ -147,8 +433,6 @@ definitions:
- password - password
- username - username
type: object type: object
api.Metadata:
type: object
api.MetricsQuery: api.MetricsQuery:
properties: properties:
interval_sec: interval_sec:
@ -157,7 +441,7 @@ definitions:
items: items:
$ref: '#/definitions/api.MetricsQueryMetric' $ref: '#/definitions/api.MetricsQueryMetric'
type: array type: array
timeframe_sec: timerange_sec:
type: integer type: integer
type: object type: object
api.MetricsQueryMetric: api.MetricsQueryMetric:
@ -171,10 +455,14 @@ definitions:
type: object type: object
api.MetricsResponse: api.MetricsResponse:
properties: properties:
interval_sec:
type: integer
metrics: metrics:
items: items:
$ref: '#/definitions/api.MetricsResponseMetric' $ref: '#/definitions/api.MetricsResponseMetric'
type: array type: array
timerange_sec:
type: integer
type: object type: object
api.MetricsResponseMetric: api.MetricsResponseMetric:
properties: properties:
@ -186,17 +474,21 @@ definitions:
type: string type: string
values: values:
items: items:
items: $ref: '#/definitions/api.MetricsResponseValue'
type: array
type: object
api.MetricsResponseValue:
properties:
ts:
type: string
value:
type: number type: number
type: array
type: array
type: object type: object
api.PlayoutStatus: api.PlayoutStatus:
properties: properties:
aqueue: aqueue:
type: integer type: integer
debug: debug: {}
type: object
drop: drop:
type: integer type: integer
dup: dup:
@ -211,19 +503,16 @@ definitions:
type: string type: string
input: input:
$ref: '#/definitions/api.PlayoutStatusIO' $ref: '#/definitions/api.PlayoutStatusIO'
type: object
looping: looping:
type: boolean type: boolean
output: output:
$ref: '#/definitions/api.PlayoutStatusIO' $ref: '#/definitions/api.PlayoutStatusIO'
type: object
queue: queue:
type: integer type: integer
stream: stream:
type: integer type: integer
swap: swap:
$ref: '#/definitions/api.PlayoutStatusSwap' $ref: '#/definitions/api.PlayoutStatusSwap'
type: object
url: url:
type: string type: string
type: object type: object
@ -307,22 +596,17 @@ definitions:
properties: properties:
config: config:
$ref: '#/definitions/api.ProcessConfig' $ref: '#/definitions/api.ProcessConfig'
type: object
created_at: created_at:
type: integer type: integer
id: id:
type: string type: string
metadata: metadata: {}
$ref: '#/definitions/api.Metadata'
type: object
reference: reference:
type: string type: string
report: report:
$ref: '#/definitions/api.ProcessReport' $ref: '#/definitions/api.ProcessReport'
type: object
state: state:
$ref: '#/definitions/api.ProcessState' $ref: '#/definitions/api.ProcessState'
type: object
type: type:
type: string type: string
type: object type: object
@ -338,7 +622,6 @@ definitions:
type: array type: array
limits: limits:
$ref: '#/definitions/api.ProcessConfigLimits' $ref: '#/definitions/api.ProcessConfigLimits'
type: object
options: options:
items: items:
type: string type: string
@ -358,17 +641,20 @@ definitions:
type: type:
enum: enum:
- ffmpeg - ffmpeg
- ""
type: string type: string
required: required:
- id
- input - input
- output - output
- type
type: object type: object
api.ProcessConfigIO: api.ProcessConfigIO:
properties: properties:
address: address:
type: string type: string
cleanup:
items:
$ref: '#/definitions/api.ProcessConfigIOCleanup'
type: array
id: id:
type: string type: string
options: options:
@ -377,7 +663,19 @@ definitions:
type: array type: array
required: required:
- address - address
- id type: object
api.ProcessConfigIOCleanup:
properties:
max_file_age_seconds:
type: integer
max_files:
type: integer
pattern:
type: string
purge_on_delete:
type: boolean
required:
- pattern
type: object type: object
api.ProcessConfigLimits: api.ProcessConfigLimits:
properties: properties:
@ -440,7 +738,6 @@ definitions:
type: string type: string
progress: progress:
$ref: '#/definitions/api.Progress' $ref: '#/definitions/api.Progress'
type: object
reconnect_seconds: reconnect_seconds:
type: integer type: integer
runtime_seconds: runtime_seconds:
@ -486,7 +783,6 @@ definitions:
avstream: avstream:
$ref: '#/definitions/api.AVstream' $ref: '#/definitions/api.AVstream'
description: avstream description: avstream
type: object
bitrate_kbit: bitrate_kbit:
description: kbit/s description: kbit/s
type: number type: number
@ -589,11 +885,9 @@ definitions:
properties: properties:
active: active:
$ref: '#/definitions/api.SessionSummaryActive' $ref: '#/definitions/api.SessionSummaryActive'
type: object
summary: summary:
$ref: '#/definitions/api.SessionSummarySummary' $ref: '#/definitions/api.SessionSummarySummary'
type: object type: object
type: object
api.SessionSummaryActive: api.SessionSummaryActive:
properties: properties:
bandwidth_rx_mbit: bandwidth_rx_mbit:
@ -795,8 +1089,10 @@ definitions:
enable_prometheus: enable_prometheus:
type: boolean type: boolean
interval_sec: interval_sec:
description: seconds
type: integer type: integer
range_sec: range_sec:
description: seconds
type: integer type: integer
type: object type: object
name: name:
@ -820,6 +1116,8 @@ definitions:
additionalProperties: additionalProperties:
type: string type: string
type: object type: object
ui_path:
type: string
type: object type: object
rtmp: rtmp:
properties: properties:
@ -843,7 +1141,7 @@ definitions:
url: url:
type: string type: string
type: object type: object
stats: sessions:
properties: properties:
enable: enable:
type: boolean type: boolean
@ -925,6 +1223,8 @@ definitions:
key_file: key_file:
type: string type: string
type: object type: object
update_check:
type: boolean
version: version:
type: integer type: integer
type: object type: object
@ -1114,14 +1414,14 @@ definitions:
info: info:
contact: contact:
email: hello@datarhei.com email: hello@datarhei.com
name: datarheiCORE Support name: datarhei Core Support
url: https://www.datarhei.com url: https://www.datarhei.com
description: Expose REST API for the datarheiCORE description: Expose REST API for the datarhei Core
license: license:
name: ??? name: Apache 2.0
url: nothing url: https://github.com/datarhei/core/blob/main/LICENSE
title: datarhei Core API title: datarhei Core API
version: "6.0" version: "3.0"
paths: paths:
/{path}: /{path}:
get: get:
@ -1173,7 +1473,8 @@ paths:
produces: produces:
- text/html - text/html
responses: responses:
"200": {} "200":
description: ""
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Load GraphQL playground summary: Load GraphQL playground
@ -1639,8 +1940,7 @@ paths:
responses: responses:
"200": "200":
description: OK description: OK
schema: schema: {}
$ref: '#/definitions/api.Metadata'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -1667,15 +1967,13 @@ paths:
in: body in: body
name: data name: data
required: true required: true
schema: schema: {}
$ref: '#/definitions/api.Metadata'
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema: {}
$ref: '#/definitions/api.Metadata'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -1943,8 +2241,7 @@ paths:
responses: responses:
"200": "200":
description: OK description: OK
schema: schema: {}
$ref: '#/definitions/api.Metadata'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -1977,15 +2274,13 @@ paths:
in: body in: body
name: data name: data
required: true required: true
schema: schema: {}
$ref: '#/definitions/api.Metadata'
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema: {}
$ref: '#/definitions/api.Metadata'
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@ -2340,10 +2635,6 @@ paths:
description: Sessions summary description: Sessions summary
schema: schema:
$ref: '#/definitions/api.SessionsSummary' $ref: '#/definitions/api.SessionsSummary'
"404":
description: Not Found
schema:
$ref: '#/definitions/api.Error'
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Get a summary of all active and past sessions summary: Get a summary of all active and past sessions
@ -2364,10 +2655,6 @@ paths:
description: Active sessions listing description: Active sessions listing
schema: schema:
$ref: '#/definitions/api.SessionsActive' $ref: '#/definitions/api.SessionsActive'
"404":
description: Not Found
schema:
$ref: '#/definitions/api.Error'
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Get a minimal summary of all active sessions summary: Get a minimal summary of all active sessions

View file

@ -17,10 +17,7 @@ func DevicesV4L() ([]HWDevice, error) {
cmd := exec.Command("v4l2-ctl", "--list-devices") cmd := exec.Command("v4l2-ctl", "--list-devices")
cmd.Env = []string{} cmd.Env = []string{}
cmd.Stdout = buf cmd.Stdout = buf
err := cmd.Run() cmd.Run()
if err != nil {
return devices, err
}
devices = parseV4LDevices(buf) devices = parseV4LDevices(buf)

42
go.mod
View file

@ -3,41 +3,37 @@ module github.com/datarhei/core
go 1.16 go 1.16
require ( require (
github.com/99designs/gqlgen v0.17.1 github.com/99designs/gqlgen v0.17.9
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/atrox/haikunatorgo/v2 v2.0.1 github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/datarhei/joy4 v0.0.0-20210125162555-2102a8289cce github.com/datarhei/joy4 v0.0.0-20210125162555-2102a8289cce
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.21.1 // indirect github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-playground/validator/v10 v10.10.1 github.com/go-playground/validator/v10 v10.11.0
github.com/golang-jwt/jwt/v4 v4.3.0 github.com/golang-jwt/jwt/v4 v4.4.1
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/labstack/echo/v4 v4.7.0 github.com/labstack/echo/v4 v4.7.2
github.com/lithammer/shortuuid/v4 v4.0.0 github.com/lithammer/shortuuid/v4 v4.0.0
github.com/lufia/plan9stats v0.0.0-20220305071607-d0b38dbe16db // indirect github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-isatty v0.0.14
github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
github.com/prometheus/client_golang v1.12.1 github.com/prometheus/client_golang v1.12.2
github.com/shirou/gopsutil/v3 v3.22.2 github.com/prometheus/common v0.34.0 // indirect
github.com/stretchr/testify v1.7.0 github.com/shirou/gopsutil/v3 v3.22.4
github.com/swaggo/echo-swagger v1.3.0 github.com/stretchr/testify v1.7.1
github.com/swaggo/swag v1.8.0 github.com/swaggo/echo-swagger v1.3.2
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/swaggo/swag v1.8.2
github.com/vektah/gqlparser/v2 v2.4.1 github.com/tklauser/numcpus v0.5.0 // indirect
github.com/vektah/gqlparser/v2 v2.4.4
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/mod v0.5.1 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.27.1 // indirect
) )

138
go.sum
View file

@ -31,20 +31,17 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.17.1 h1:i2qQMPKHQjHgBWYIpO4TsaQpPqMHCPK1+h95ipvH8VU= github.com/99designs/gqlgen v0.17.9 h1:0XvE3nMaTaLYq7XbBz1MY0t9BFcntydlt1zzNa4eY+4=
github.com/99designs/gqlgen v0.17.1/go.mod h1:K5fzLKwtph+FFgh9j7nFbRUdBKvTcGnsta51fsMTn3o= github.com/99designs/gqlgen v0.17.9/go.mod h1:PThAZAK9t2pAat7g8QdSI4dCBMOhBO+t2qj+0jvDqps=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d h1:4BQNwS4T13UU3Yee4GfzZH3Q9SNpKeJvLigfw8fDjX0= github.com/alecthomas/jsonschema v0.0.0-20211228220459-151e3c21f49d h1:4BQNwS4T13UU3Yee4GfzZH3Q9SNpKeJvLigfw8fDjX0=
@ -83,7 +80,6 @@ github.com/datarhei/joy4 v0.0.0-20210125162555-2102a8289cce/go.mod h1:Jcw/6jZDQQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -97,18 +93,22 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 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 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= 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.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
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.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
@ -119,14 +119,14 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -167,8 +167,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -185,7 +186,6 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -223,10 +223,8 @@ 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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.14/go.mod h1:Q5KZ1vD3V5FEzjM79hjwVrC3ABr7F5IdM23bXQMRDGg= github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
github.com/labstack/echo/v4 v4.7.0 h1:8wHgZhoE9OT1NSLw6sfrX7ZGpWMtO5Zlfr68+BIo180= github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/echo/v4 v4.7.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
@ -235,31 +233,25 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20220305071607-d0b38dbe16db h1:QT3DrSQsMWGKZMArbkP9FlS2ZnPLA2z8D7fU+G3BZ3o= github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 h1:aczX6NMOtt6L4YT0fQvKkDK6LZEtdOso9sUH89V1+P0=
github.com/lufia/plan9stats v0.0.0-20220305071607-d0b38dbe16db/go.mod h1:VgrrWVwBO2+6XKn8ypT3WUqvoxCa8R2M5to2tRzGovI= github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281/go.mod h1:lc+czkgO/8F7puNki5jk8QyujbfK1LOT7Wl0ON2hxyk=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.2.3 h1:Q06vEqnBYjjfx5KKgHfYRKE/lvlRu+Nj+xodG4YdHnU= github.com/matryer/moq v0.2.7 h1:RtpiPUM8L7ZSCbSwK+QcZH/E9tgqAkFjKQxsRs25b4w=
github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@ -268,7 +260,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
@ -289,8 +280,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -299,8 +291,9 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE=
github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -316,8 +309,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= github.com/shirou/gopsutil/v3 v3.22.4 h1:srAQaiX6jX/cYL6q29aE0m8lOskT9CurZ9N61YR3yoI=
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shirou/gopsutil/v3 v3.22.4/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -331,32 +324,30 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/echo-swagger v1.3.0 h1:xxL/4jbCY4Z3udUvqOas+IpTMKbxrKdEKwtS7He0Qhg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/swaggo/echo-swagger v1.3.0/go.mod h1:snY6MlGK+pQAfJNEfX5qaOzt/QuM/WINVxGgQaZVJgg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/echo-swagger v1.3.2 h1:D+3BNl8JMC6pKhA+egjh4LGI0jNesqlt77WahTHfTXQ=
github.com/swaggo/echo-swagger v1.3.2/go.mod h1:Sjj0O7Puf939HXhxhfZdR49MIrtcg3mLgdg3/qVcbyw=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.8.0 h1:80NNhvpJcuItNpBDqgJwDuKlMmaZ/OATOzhG3bhcM3w= github.com/swaggo/swag v1.8.2 h1:D4aBiVS2a65zhyk3WFqOUz7Rz0sOaUcgeErcid5uGL4=
github.com/swaggo/swag v1.8.0/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= github.com/swaggo/swag v1.8.2/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.4.0/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/vektah/gqlparser/v2 v2.4.4 h1:rh9hwZ5Jx9cCq88zXz2YHKmuQBuwY1JErHU8GywFdwE=
github.com/vektah/gqlparser/v2 v2.4.1 h1:QOyEn8DAPMUMARGMeshKDkDgNmVoEaEGiDB0uWxcSlQ= github.com/vektah/gqlparser/v2 v2.4.4/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
github.com/vektah/gqlparser/v2 v2.4.1/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@ -364,6 +355,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -381,13 +374,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -419,8 +412,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -456,14 +450,17 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -478,7 +475,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -487,15 +483,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -520,17 +513,16 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -545,8 +537,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -587,11 +579,11 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -672,8 +664,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -17,11 +17,11 @@ type ProbeIO struct {
Type string `json:"type"` Type string `json:"type"`
Codec string `json:"codec"` Codec string `json:"codec"`
Coder string `json:"coder"` Coder string `json:"coder"`
Bitrate json.Number `json:"bitrate_kbps"` Bitrate json.Number `json:"bitrate_kbps" swaggertype:"number"`
Duration json.Number `json:"duration_sec"` Duration json.Number `json:"duration_sec" swaggertype:"number"`
// video // video
FPS json.Number `json:"fps"` FPS json.Number `json:"fps" swaggertype:"number"`
Pixfmt string `json:"pix_fmt"` Pixfmt string `json:"pix_fmt"`
Width uint64 `json:"width"` Width uint64 `json:"width"`
Height uint64 `json:"height"` Height uint64 `json:"height"`

View file

@ -32,6 +32,7 @@ type ProcessConfigIOCleanup struct {
Pattern string `json:"pattern" validate:"required"` Pattern string `json:"pattern" validate:"required"`
MaxFiles uint `json:"max_files"` MaxFiles uint `json:"max_files"`
MaxFileAge uint `json:"max_file_age_seconds"` MaxFileAge uint `json:"max_file_age_seconds"`
PurgeOnDelete bool `json:"purge_on_delete"`
} }
type ProcessConfigLimits struct { type ProcessConfigLimits struct {
@ -94,6 +95,7 @@ func (cfg *ProcessConfig) Marshal() *app.Config {
Pattern: c.Pattern, Pattern: c.Pattern,
MaxFiles: c.MaxFiles, MaxFiles: c.MaxFiles,
MaxFileAge: c.MaxFileAge, MaxFileAge: c.MaxFileAge,
PurgeOnDelete: c.PurgeOnDelete,
}) })
} }
@ -237,7 +239,7 @@ type ProcessState struct {
LastLog string `json:"last_logline"` LastLog string `json:"last_logline"`
Progress *Progress `json:"progress"` Progress *Progress `json:"progress"`
Memory uint64 `json:"memory_bytes"` Memory uint64 `json:"memory_bytes"`
CPU json.Number `json:"cpu_usage"` CPU json.Number `json:"cpu_usage" swaggertype:"number"`
Command []string `json:"command"` Command []string `json:"command"`
} }

View file

@ -20,15 +20,15 @@ type ProgressIO struct {
Codec string `json:"codec"` Codec string `json:"codec"`
Coder string `json:"coder"` Coder string `json:"coder"`
Frame uint64 `json:"frame"` Frame uint64 `json:"frame"`
FPS json.Number `json:"fps"` FPS json.Number `json:"fps" swaggertype:"number"`
Packet uint64 `json:"packet"` Packet uint64 `json:"packet"`
PPS json.Number `json:"pps"` PPS json.Number `json:"pps" swaggertype:"number"`
Size uint64 `json:"size_kb"` // kbytes Size uint64 `json:"size_kb"` // kbytes
Bitrate json.Number `json:"bitrate_kbit"` // kbit/s Bitrate json.Number `json:"bitrate_kbit" swaggertype:"number"` // kbit/s
// Video // Video
Pixfmt string `json:"pix_fmt,omitempty"` Pixfmt string `json:"pix_fmt,omitempty"`
Quantizer json.Number `json:"q,omitempty"` Quantizer json.Number `json:"q,omitempty" swaggertype:"number"`
Width uint64 `json:"width,omitempty"` Width uint64 `json:"width,omitempty"`
Height uint64 `json:"height,omitempty"` Height uint64 `json:"height,omitempty"`
@ -81,12 +81,12 @@ type Progress struct {
Output []ProgressIO `json:"outputs"` Output []ProgressIO `json:"outputs"`
Frame uint64 `json:"frame"` Frame uint64 `json:"frame"`
Packet uint64 `json:"packet"` Packet uint64 `json:"packet"`
FPS json.Number `json:"fps"` FPS json.Number `json:"fps" swaggertype:"number"`
Quantizer json.Number `json:"q"` Quantizer json.Number `json:"q" swaggertype:"number"`
Size uint64 `json:"size_kb"` // kbytes Size uint64 `json:"size_kb"` // kbytes
Time json.Number `json:"time"` Time json.Number `json:"time" swaggertype:"number"`
Bitrate json.Number `json:"bitrate_kbit"` // kbit/s Bitrate json.Number `json:"bitrate_kbit" swaggertype:"number"` // kbit/s
Speed json.Number `json:"speed"` Speed json.Number `json:"speed" swaggertype:"number"`
Drop uint64 `json:"drop"` Drop uint64 `json:"drop"`
Dup uint64 `json:"dup"` Dup uint64 `json:"dup"`
} }

View file

@ -30,8 +30,8 @@ type Session struct {
Extra string `json:"extra"` Extra string `json:"extra"`
RxBytes uint64 `json:"bytes_rx"` RxBytes uint64 `json:"bytes_rx"`
TxBytes uint64 `json:"bytes_tx"` TxBytes uint64 `json:"bytes_tx"`
RxBitrate json.Number `json:"bandwidth_rx_kbit"` // kbit/s RxBitrate json.Number `json:"bandwidth_rx_kbit" swaggertype:"number"` // kbit/s
TxBitrate json.Number `json:"bandwidth_tx_kbit"` // kbit/s TxBitrate json.Number `json:"bandwidth_tx_kbit" swaggertype:"number"` // kbit/s
} }
func (s *Session) Unmarshal(sess session.Session) { func (s *Session) Unmarshal(sess session.Session) {
@ -51,11 +51,11 @@ func (s *Session) Unmarshal(sess session.Session) {
type SessionSummaryActive struct { type SessionSummaryActive struct {
SessionList []Session `json:"list"` SessionList []Session `json:"list"`
Sessions uint64 `json:"sessions"` Sessions uint64 `json:"sessions"`
RxBitrate json.Number `json:"bandwidth_rx_mbit"` // mbit/s RxBitrate json.Number `json:"bandwidth_rx_mbit" swaggertype:"number"` // mbit/s
TxBitrate json.Number `json:"bandwidth_tx_mbit"` // mbit/s TxBitrate json.Number `json:"bandwidth_tx_mbit" swaggertype:"number"` // mbit/s
MaxSessions uint64 `json:"max_sessions"` MaxSessions uint64 `json:"max_sessions"`
MaxRxBitrate json.Number `json:"max_bandwidth_rx_mbit"` // mbit/s MaxRxBitrate json.Number `json:"max_bandwidth_rx_mbit" swaggertype:"number"` // mbit/s
MaxTxBitrate json.Number `json:"max_bandwidth_tx_mbit"` // mbit/s MaxTxBitrate json.Number `json:"max_bandwidth_tx_mbit" swaggertype:"number"` // mbit/s
} }
// SessionSummarySummary represents the summary (history) of all finished sessions // SessionSummarySummary represents the summary (history) of all finished sessions

View file

@ -2,6 +2,7 @@ package api
import ( import (
"net/http" "net/http"
"path/filepath"
"sort" "sort"
"github.com/datarhei/core/http/api" "github.com/datarhei/core/http/api"
@ -64,6 +65,8 @@ func (h *DiskFSHandler) GetFile(c echo.Context) error {
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")) c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
if path, ok := stat.IsLink(); ok { if path, ok := stat.IsLink(); ok {
path = filepath.Clean("/" + path)
if path[0] == '/' { if path[0] == '/' {
path = path[1:] path = path[1:]
} }

View file

@ -31,7 +31,6 @@ func NewSession(registry session.Registry) *SessionHandler {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param collectors query string false "Comma separated list of collectors" // @Param collectors query string false "Comma separated list of collectors"
// @Success 200 {object} api.SessionsSummary "Sessions summary" // @Success 200 {object} api.SessionsSummary "Sessions summary"
// @Failure 404 {object} api.Error
// @Router /api/v3/session [get] // @Router /api/v3/session [get]
func (s *SessionHandler) Summary(c echo.Context) error { func (s *SessionHandler) Summary(c echo.Context) error {
collectors := strings.Split(util.DefaultQuery(c, "collectors", ""), ",") collectors := strings.Split(util.DefaultQuery(c, "collectors", ""), ",")
@ -39,13 +38,8 @@ func (s *SessionHandler) Summary(c echo.Context) error {
sessionsSummary := make(api.SessionsSummary) sessionsSummary := make(api.SessionsSummary)
for _, name := range collectors { for _, name := range collectors {
collector := s.registry.Collector(name)
if collector == nil {
return api.Err(http.StatusNotFound, "Unknown collector", "Registered collectors are: %s", strings.Join(s.registry.Collectors(), ", "))
}
summary := api.SessionSummary{} summary := api.SessionSummary{}
summary.Unmarshal(collector.Summary()) summary.Unmarshal(s.registry.Summary(name))
sessionsSummary[name] = summary sessionsSummary[name] = summary
} }
@ -61,7 +55,6 @@ func (s *SessionHandler) Summary(c echo.Context) error {
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param collectors query string false "Comma separated list of collectors" // @Param collectors query string false "Comma separated list of collectors"
// @Success 200 {object} api.SessionsActive "Active sessions listing" // @Success 200 {object} api.SessionsActive "Active sessions listing"
// @Failure 404 {object} api.Error
// @Router /api/v3/session/active [get] // @Router /api/v3/session/active [get]
func (s *SessionHandler) Active(c echo.Context) error { func (s *SessionHandler) Active(c echo.Context) error {
collectors := strings.Split(util.DefaultQuery(c, "collectors", ""), ",") collectors := strings.Split(util.DefaultQuery(c, "collectors", ""), ",")
@ -69,14 +62,10 @@ func (s *SessionHandler) Active(c echo.Context) error {
sessionsActive := make(api.SessionsActive) sessionsActive := make(api.SessionsActive)
for _, name := range collectors { for _, name := range collectors {
collector := s.registry.Collector(name) sessions := s.registry.Active(name)
if collector == nil { active := []api.Session{}
return api.Err(http.StatusNotFound, "Unknown collector", "Registered collectors are: %s", strings.Join(s.registry.Collectors(), ", "))
}
sessions := collector.Active() active = make([]api.Session, len(sessions))
active := make([]api.Session, len(sessions))
for i, s := range sessions { for i, s := range sessions {
active[i].Unmarshal(s) active[i].Unmarshal(s)

View file

@ -69,6 +69,8 @@ func (h *DiskFSHandler) GetFile(c echo.Context) error {
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")) c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
if path, ok := stat.IsLink(); ok { if path, ok := stat.IsLink(); ok {
path = filepath.Clean("/" + path)
if path[0] == '/' { if path[0] == '/' {
path = path[1:] path = path[1:]
} }

View file

@ -2,6 +2,7 @@ package handler
import ( import (
"net/http" "net/http"
"path/filepath"
"github.com/datarhei/core/http/api" "github.com/datarhei/core/http/api"
"github.com/datarhei/core/http/handler/util" "github.com/datarhei/core/http/handler/util"
@ -51,6 +52,8 @@ func (h *MemFSHandler) GetFile(c echo.Context) error {
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")) c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
if path, ok := stat.IsLink(); ok { if path, ok := stat.IsLink(); ok {
path = filepath.Clean("/" + path)
if path[0] == '/' { if path[0] == '/' {
path = path[1:] path = path[1:]
} }

View file

@ -55,7 +55,11 @@ func NewHLSWithConfig(config HLSConfig) echo.MiddlewareFunc {
} }
if config.EgressCollector == nil { if config.EgressCollector == nil {
config.EgressCollector = DefaultHTTPConfig.Collector config.EgressCollector = DefaultHLSConfig.EgressCollector
}
if config.IngressCollector == nil {
config.IngressCollector = DefaultHLSConfig.IngressCollector
} }
hls := hls{ hls := hls{

View file

@ -1,13 +1,13 @@
// @title datarhei Core API // @title datarhei Core API
// @version 6.0 // @version 3.0
// @description Expose REST API for the datarheiCORE // @description Expose REST API for the datarhei Core
// @contact.name datarheiCORE Support // @contact.name datarhei Core Support
// @contact.url https://www.datarhei.com // @contact.url https://www.datarhei.com
// @contact.email hello@datarhei.com // @contact.email hello@datarhei.com
// @license.name ??? // @license.name Apache 2.0
// @license.url nothing // @license.url https://github.com/datarhei/core/blob/main/LICENSE
// @BasePath / // @BasePath /

View file

@ -204,17 +204,13 @@ func (fs *diskFilesystem) Files() int64 {
} }
func (fs *diskFilesystem) Symlink(oldname, newname string) error { func (fs *diskFilesystem) Symlink(oldname, newname string) error {
if !filepath.IsAbs(oldname) { oldname = filepath.Join(fs.dir, filepath.Clean("/"+oldname))
return nil
}
oldname = filepath.Join(fs.dir, oldname)
if !filepath.IsAbs(newname) { if !filepath.IsAbs(newname) {
return nil return nil
} }
newname = filepath.Join(fs.dir, newname) newname = filepath.Join(fs.dir, filepath.Clean("/"+newname))
err := os.Symlink(oldname, newname) err := os.Symlink(oldname, newname)
@ -222,11 +218,7 @@ func (fs *diskFilesystem) Symlink(oldname, newname string) error {
} }
func (fs *diskFilesystem) Open(path string) File { func (fs *diskFilesystem) Open(path string) File {
if !filepath.IsAbs(path) { path = filepath.Join(fs.dir, filepath.Clean("/"+path))
return nil
}
path = filepath.Join(fs.dir, path)
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
@ -243,16 +235,7 @@ func (fs *diskFilesystem) Open(path string) File {
} }
func (fs *diskFilesystem) Store(path string, r io.Reader) (int64, bool, error) { func (fs *diskFilesystem) Store(path string, r io.Reader) (int64, bool, error) {
if !filepath.IsAbs(path) { path = filepath.Join(fs.dir, filepath.Clean("/"+path))
return -1, false, fmt.Errorf("Invalid path")
}
path = filepath.Join(fs.dir, path)
// Check that we're not leaving our base directory
if !strings.HasPrefix(path, fs.dir) {
return -1, false, fmt.Errorf("Invalid path")
}
replace := true replace := true
@ -283,11 +266,7 @@ func (fs *diskFilesystem) Store(path string, r io.Reader) (int64, bool, error) {
} }
func (fs *diskFilesystem) Delete(path string) int64 { func (fs *diskFilesystem) Delete(path string) int64 {
if !filepath.IsAbs(path) { path = filepath.Join(fs.dir, filepath.Clean("/"+path))
return -1
}
path = filepath.Join(fs.dir, path)
finfo, err := os.Stat(path) finfo, err := os.Stat(path)
if err != nil { if err != nil {

View file

@ -270,7 +270,13 @@ func (e *Event) WithField(key string, value interface{}) Logger {
}) })
} }
const maxFields = 1024
func (e *Event) WithFields(f Fields) Logger { func (e *Event) WithFields(f Fields) Logger {
if maxFields-len(e.Data)-len(f) < 0 {
return e
}
data := make(Fields, len(e.Data)+len(f)) data := make(Fields, len(e.Data)+len(f))
for k, v := range e.Data { for k, v := range e.Data {
data[k] = v data[k] = v

View file

@ -6,6 +6,7 @@ type ConfigIOCleanup struct {
Pattern string `json:"pattern"` Pattern string `json:"pattern"`
MaxFiles uint `json:"max_files"` MaxFiles uint `json:"max_files"`
MaxFileAge uint `json:"max_file_age_seconds"` MaxFileAge uint `json:"max_file_age_seconds"`
PurgeOnDelete bool `json:"purge_on_delete"`
} }
type ConfigIO struct { type ConfigIO struct {

View file

@ -19,6 +19,7 @@ type Pattern struct {
Pattern string Pattern string
MaxFiles uint MaxFiles uint
MaxFileAge time.Duration MaxFileAge time.Duration
PurgeOnDelete bool
} }
type Filesystem interface { type Filesystem interface {
@ -116,7 +117,11 @@ func (fs *filesystem) UnsetCleanup(id string) {
fs.cleanupLock.Lock() fs.cleanupLock.Lock()
defer fs.cleanupLock.Unlock() defer fs.cleanupLock.Unlock()
patterns, _ := fs.cleanupPatterns[id]
delete(fs.cleanupPatterns, id) delete(fs.cleanupPatterns, id)
fs.purge(patterns)
} }
func (fs *filesystem) cleanup() { func (fs *filesystem) cleanup() {
@ -150,6 +155,20 @@ func (fs *filesystem) cleanup() {
} }
} }
func (fs *filesystem) purge(patterns []Pattern) {
for _, pattern := range patterns {
if !pattern.PurgeOnDelete {
continue
}
files := fs.Filesystem.List(pattern.Pattern)
for _, f := range files {
fs.logger.Debug().WithField("path", f.Name()).Log("Purging file")
fs.Filesystem.Delete(f.Name())
}
}
}
func (fs *filesystem) cleanupTicker(ctx context.Context, interval time.Duration) { func (fs *filesystem) cleanupTicker(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
defer ticker.Stop() defer ticker.Stop()

View file

@ -63,6 +63,7 @@ type Config struct {
} }
type task struct { type task struct {
valid bool
id string // ID of the task/process id string // ID of the task/process
reference string reference string
process *app.Process process *app.Process
@ -188,7 +189,9 @@ func (r *restream) Stop() {
// altering their order such that on a subsequent // altering their order such that on a subsequent
// Start() they will get restarted. // Start() they will get restarted.
for id, t := range r.tasks { for id, t := range r.tasks {
if t.ffmpeg != nil {
t.ffmpeg.Stop() t.ffmpeg.Stop()
}
r.unsetCleanup(id) r.unsetCleanup(id)
} }
@ -221,6 +224,10 @@ func (r *restream) observe(ctx context.Context, interval time.Duration) {
// Stop all tasks that write to disk // Stop all tasks that write to disk
r.lock.Lock() r.lock.Lock()
for id, t := range r.tasks { for id, t := range r.tasks {
if !t.valid {
continue
}
if !t.usesDisk { if !t.usesDisk {
continue continue
} }
@ -244,8 +251,7 @@ func (r *restream) load() error {
return err return err
} }
r.metadata = data.Metadata.System tasks := make(map[string]*task)
r.tasks = make(map[string]*task)
for id, process := range data.Process { for id, process := range data.Process {
t := &task{ t := &task{
@ -259,11 +265,11 @@ func (r *restream) load() error {
// Replace all placeholders in the config // Replace all placeholders in the config
r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base()) r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base())
r.tasks[id] = t tasks[id] = t
} }
for id, userdata := range data.Metadata.Process { for id, userdata := range data.Metadata.Process {
t, ok := r.tasks[id] t, ok := tasks[id]
if !ok { if !ok {
continue continue
} }
@ -274,20 +280,23 @@ func (r *restream) load() error {
// Now that all tasks are defined and all placeholders are // Now that all tasks are defined and all placeholders are
// replaced, we can resolve references and validate the // replaced, we can resolve references and validate the
// inputs and outputs. // inputs and outputs.
for _, t := range r.tasks { for _, t := range tasks {
err := r.resolveAddresses(t.config) err := r.resolveAddresses(tasks, t.config)
if err != nil { if err != nil {
return err r.logger.Warn().WithField("id", t.id).WithError(err).Log("Ignoring")
continue
} }
t.usesDisk, err = r.validateConfig(t.config) t.usesDisk, err = r.validateConfig(t.config)
if err != nil { if err != nil {
return err r.logger.Warn().WithField("id", t.id).WithError(err).Log("Ignoring")
continue
} }
err = r.setPlayoutPorts(t) err = r.setPlayoutPorts(t)
if err != nil { if err != nil {
return err r.logger.Warn().WithField("id", t.id).WithError(err).Log("Ignoring")
continue
} }
t.command = r.createCommand(t.config) t.command = r.createCommand(t.config)
@ -306,8 +315,12 @@ func (r *restream) load() error {
} }
t.ffmpeg = ffmpeg t.ffmpeg = ffmpeg
t.valid = true
} }
r.tasks = tasks
r.metadata = data.Metadata.System
return nil return nil
} }
@ -399,7 +412,7 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base()) r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base())
err := r.resolveAddresses(t.config) err := r.resolveAddresses(r.tasks, t.config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -430,6 +443,7 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
} }
t.ffmpeg = ffmpeg t.ffmpeg = ffmpeg
t.valid = true
return t, nil return t, nil
} }
@ -443,6 +457,7 @@ func (r *restream) setCleanup(id string, config *app.Config) {
Pattern: strings.TrimPrefix(c.Pattern, "memfs:"), Pattern: strings.TrimPrefix(c.Pattern, "memfs:"),
MaxFiles: c.MaxFiles, MaxFiles: c.MaxFiles,
MaxFileAge: time.Duration(c.MaxFileAge) * time.Second, MaxFileAge: time.Duration(c.MaxFileAge) * time.Second,
PurgeOnDelete: c.PurgeOnDelete,
}, },
}) })
} else if strings.HasPrefix(c.Pattern, "diskfs:") { } else if strings.HasPrefix(c.Pattern, "diskfs:") {
@ -451,6 +466,7 @@ func (r *restream) setCleanup(id string, config *app.Config) {
Pattern: strings.TrimPrefix(c.Pattern, "diskfs:"), Pattern: strings.TrimPrefix(c.Pattern, "diskfs:"),
MaxFiles: c.MaxFiles, MaxFiles: c.MaxFiles,
MaxFileAge: time.Duration(c.MaxFileAge) * time.Second, MaxFileAge: time.Duration(c.MaxFileAge) * time.Second,
PurgeOnDelete: c.PurgeOnDelete,
}, },
}) })
} }
@ -754,10 +770,10 @@ func (r *restream) validateOutputAddress(address, basedir string) (string, bool,
return "file:" + address, true, nil return "file:" + address, true, nil
} }
func (r *restream) resolveAddresses(config *app.Config) error { func (r *restream) resolveAddresses(tasks map[string]*task, config *app.Config) error {
for i, input := range config.Input { for i, input := range config.Input {
// Resolve any references // Resolve any references
address, err := r.resolveAddress(config.ID, input.Address) address, err := r.resolveAddress(tasks, config.ID, input.Address)
if err != nil { if err != nil {
return fmt.Errorf("reference error for '#%s:%s': %w", config.ID, input.ID, err) return fmt.Errorf("reference error for '#%s:%s': %w", config.ID, input.ID, err)
} }
@ -770,7 +786,7 @@ func (r *restream) resolveAddresses(config *app.Config) error {
return nil return nil
} }
func (r *restream) resolveAddress(id, address string) (string, error) { func (r *restream) resolveAddress(tasks map[string]*task, id, address string) (string, error) {
re := regexp.MustCompile(`^#(.+):output=(.+)`) re := regexp.MustCompile(`^#(.+):output=(.+)`)
if len(address) == 0 { if len(address) == 0 {
@ -790,7 +806,7 @@ func (r *restream) resolveAddress(id, address string) (string, error) {
return address, fmt.Errorf("self-reference not possible (%s)", address) return address, fmt.Errorf("self-reference not possible (%s)", address)
} }
task, ok := r.tasks[matches[1]] task, ok := tasks[matches[1]]
if !ok { if !ok {
return address, fmt.Errorf("unknown process '%s' (%s)", matches[1], address) return address, fmt.Errorf("unknown process '%s' (%s)", matches[1], address)
} }
@ -886,6 +902,10 @@ func (r *restream) startProcess(id string) error {
return fmt.Errorf("unknown process ID (%s)", id) return fmt.Errorf("unknown process ID (%s)", id)
} }
if !task.valid {
return fmt.Errorf("invalid process definition")
}
status := task.ffmpeg.Status() status := task.ffmpeg.Status()
if task.process.Order == "start" && status.Order == "start" { if task.process.Order == "start" && status.Order == "start" {
@ -953,6 +973,10 @@ func (r *restream) restartProcess(id string) error {
return fmt.Errorf("unknown process ID (%s)", id) return fmt.Errorf("unknown process ID (%s)", id)
} }
if !task.valid {
return fmt.Errorf("invalid process definition")
}
if task.process.Order == "stop" { if task.process.Order == "stop" {
return nil return nil
} }
@ -982,11 +1006,13 @@ func (r *restream) reloadProcess(id string) error {
return fmt.Errorf("unknown process ID (%s)", id) return fmt.Errorf("unknown process ID (%s)", id)
} }
t.valid = false
t.config = t.process.Config.Clone() t.config = t.process.Config.Clone()
r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base()) r.resolvePlaceholders(t.config, r.fs.diskfs.Base(), r.fs.memfs.Base())
err := r.resolveAddresses(t.config) err := r.resolveAddresses(r.tasks, t.config)
if err != nil { if err != nil {
return err return err
} }
@ -1024,6 +1050,7 @@ func (r *restream) reloadProcess(id string) error {
} }
t.ffmpeg = ffmpeg t.ffmpeg = ffmpeg
t.valid = true
if order == "start" { if order == "start" {
r.startProcess(id) r.startProcess(id)
@ -1043,6 +1070,10 @@ func (r *restream) GetProcessState(id string) (*app.State, error) {
return state, fmt.Errorf("unknown process ID (%s)", id) return state, fmt.Errorf("unknown process ID (%s)", id)
} }
if !task.valid {
return state, fmt.Errorf("invalid process definition")
}
status := task.ffmpeg.Status() status := task.ffmpeg.Status()
state.Order = task.process.Order state.Order = task.process.Order
@ -1100,6 +1131,10 @@ func (r *restream) GetProcessLog(id string) (*app.Log, error) {
return &app.Log{}, fmt.Errorf("unknown process ID (%s)", id) return &app.Log{}, fmt.Errorf("unknown process ID (%s)", id)
} }
if !task.valid {
return &app.Log{}, fmt.Errorf("invalid process definition")
}
log := &app.Log{} log := &app.Log{}
current := task.parser.Report() current := task.parser.Report()
@ -1150,6 +1185,10 @@ func (r *restream) Probe(id string) app.Probe {
r.lock.RUnlock() r.lock.RUnlock()
if !task.valid {
return appprobe
}
var command []string var command []string
// Copy global options // Copy global options
@ -1210,6 +1249,10 @@ func (r *restream) GetPlayout(id, inputid string) (string, error) {
return "", fmt.Errorf("unknown process ID '%s'", id) return "", fmt.Errorf("unknown process ID '%s'", id)
} }
if !task.valid {
return "", fmt.Errorf("Invalid process definition")
}
port, ok := task.playout[inputid] port, ok := task.playout[inputid]
if !ok { if !ok {
return "", fmt.Errorf("no playout for input ID '%s' and process '%s'", inputid, id) return "", fmt.Errorf("no playout for input ID '%s' and process '%s'", inputid, id)

View file

@ -24,12 +24,26 @@ type Config struct {
// The Registry interface // The Registry interface
type Registry interface { type Registry interface {
// Register returns a new collector from conf and registers it under the id and error is nil. In case of error
// the returned collector is nil and the error is not nil.
Register(id string, conf CollectorConfig) (Collector, error) Register(id string, conf CollectorConfig) (Collector, error)
// Unregister unregisters the collector with the ID, returns error if the ID is not registered
Unregister(id string) error Unregister(id string) error
// UnregisterAll unregisters al registered collectors
UnregisterAll() UnregisterAll()
// Collectors returns an array of all registered IDs
Collectors() []string Collectors() []string
// Collector returns the collector with the ID, or nil if the ID is not registered
Collector(id string) Collector Collector(id string) Collector
// Summary returns the summary from a collector with the ID, or an empty summary if the ID is not registered
Summary(id string) Summary Summary(id string) Summary
// Active returns the active sessions from a collector with the ID, or an empty list if the ID is not registered
Active(id string) []Session Active(id string) []Session
} }

View file

@ -14,3 +14,4 @@
.idea/ .idea/
*.test *.test
*.out *.out
gqlgen

View file

@ -5,12 +5,416 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<a name="unreleased"></a> <a name="unreleased"></a>
## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.1...HEAD) ## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.7...HEAD)
<!-- end of if --> <!-- end of if -->
<!-- end of CommitGroups --> <!-- end of CommitGroups -->
<a name="v0.17.7"></a>
## [v0.17.7](https://github.com/99designs/gqlgen/compare/v0.17.6...v0.17.7) - 2022-05-24
- <a href="https://github.com/99designs/gqlgen/commit/2b1dff1b71f89c95e946bbe5948b7061f9c47aa8"><tt>2b1dff1b</tt></a> release v0.17.7
- <a href="https://github.com/99designs/gqlgen/commit/b2087f944d9b9af6e776a9d97662c9e8b86a8c3b"><tt>b2087f94</tt></a> Update module dependencies (<a href="https://github.com/99designs/gqlgen/pull/92">#2192</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/8825ac460b047e22724ed7728c7d7ffbf1b523a9"><tt>8825ac46</tt></a> Fix misprint (<a href="https://github.com/99designs/gqlgen/pull/87">#2187</a>)</summary>
* Fix misprint
* Fix misprint
* Re-generate
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/41daa5d8dc1e35bdbfe68e95b37c10599b224456"><tt>41daa5d8</tt></a> fix <a href="https://github.com/99designs/gqlgen/pull/91">#2190](https://github.com/99designs/gqlgen/issues/2190) - don't use backslash for "embed" paths on windows ([#2191</a>)
- <a href="https://github.com/99designs/gqlgen/commit/0cce5544f06fd84831ef2ca0f60e16f7f554814d"><tt>0cce5544</tt></a> Update Changelog
- <a href="https://github.com/99designs/gqlgen/commit/26644541aafbcdb46f10a6ff0f5894637227c331"><tt>26644541</tt></a> v0.17.6 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.6"></a>
## [v0.17.6](https://github.com/99designs/gqlgen/compare/v0.17.5...v0.17.6) - 2022-05-23
- <a href="https://github.com/99designs/gqlgen/commit/358d45dcfc2b022fdda9476a37f44c0622607ae9"><tt>358d45dc</tt></a> release v0.17.6
- <a href="https://github.com/99designs/gqlgen/commit/7c95938c5f1278fa14a13a92eb88d117102e0330"><tt>7c95938c</tt></a> Improve operation error handling (<a href="https://github.com/99designs/gqlgen/pull/84">#2184</a>)
- <a href="https://github.com/99designs/gqlgen/commit/2526f6871166377b4f444ad8d22577a632b0abf4"><tt>2526f687</tt></a> Correct identation (<a href="https://github.com/99designs/gqlgen/pull/82">#2182</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/f7bf453c79d82b01ed4baed894043aaff645bf2f"><tt>f7bf453c</tt></a> Bump dset from 3.1.1 to 3.1.2 in /integration (<a href="https://github.com/99designs/gqlgen/pull/76">#2176</a>)</summary>
Bumps [dset](https://github.com/lukeed/dset) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/lukeed/dset/releases)
- [Commits](https://github.com/lukeed/dset/compare/v3.1.1...v3.1.2)
---
updated-dependencies:
- dependency-name: dset
dependency-type: indirect
...
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/4cdf70261a9dc8af589399f09b56d0f90606a9fa"><tt>4cdf7026</tt></a> Update getting-started.md (<a href="https://github.com/99designs/gqlgen/pull/57">#2157</a>)</summary>
Fix getting-started missing fields resolver config
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/eef7bfaad1b524f9e2fc0c1150fdb321c276069e"><tt>eef7bfaa</tt></a> fix: prevents goroutine leak at websocket transport (<a href="https://github.com/99designs/gqlgen/pull/68">#2168</a>)
- <a href="https://github.com/99designs/gqlgen/commit/b8ec51d8629a24288353b4ee4be70fff3645b03e"><tt>b8ec51d8</tt></a> go: update gqlparser to latest (<a href="https://github.com/99designs/gqlgen/pull/49">#2149</a>)
- <a href="https://github.com/99designs/gqlgen/commit/ec3e597e7b45e17464cd8c7faaa51e75755ce3cf"><tt>ec3e597e</tt></a> Fix docs bug in field collection (<a href="https://github.com/99designs/gqlgen/pull/41">#2141</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/f6b352316fae4b4fdc6317e24ea94ba48ac29e85"><tt>f6b35231</tt></a> Add argument to WebsocketErrorFunc (<a href="https://github.com/99designs/gqlgen/pull/24">#2124</a>)</summary>
* Add argument to WebsocketErrorFunc
to determine whether the error ocured on read or write to the websocket.
* Wrap websocket error
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/0f016df3ae7ee4898358dc67a491689164297df6"><tt>0f016df3</tt></a> Fix invalid query parameter for playground subscription endpoint (<a href="https://github.com/99designs/gqlgen/pull/48">#2148</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/fb5751ab478603a864977f9fbe70655776d7fb55"><tt>fb5751ab</tt></a> use "embed" in generated code (<a href="https://github.com/99designs/gqlgen/pull/19">#2119</a>)</summary>
* use "embed" in generated code
* don't use embed for builtins
* working poc
* handle no embeddable sources
* fix dir
* comment
* add test for embedding
* improve error handling
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/d38911f1a9d7f0ec39a74a95994d95291f1922c3"><tt>d38911f1</tt></a> Allow absolute https://github.com/99designs/gqlgens to the GraphQL playground (<a href="https://github.com/99designs/gqlgen/pull/42">#2142</a>)</summary>
* Allow absolute URLs to the GraphQL playground
* Add test for playground URLs
* Close res.Body in playground test
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/3228f36fec50930483801b27b92658592fab5e87"><tt>3228f36f</tt></a> Update getting-started.md (<a href="https://github.com/99designs/gqlgen/pull/40">#2140</a>)</summary>
* Update getting-started.md
function rand.Int requires two parameters and returns two value in golang version 1.18.1.
* Highlight the package used so people don't pick crypto/rand
* Revert to original
* Remove extra space
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/33fe0b9b824ec86699059f410505c02659fc6c81"><tt>33fe0b9b</tt></a> Update package.json (<a href="https://github.com/99designs/gqlgen/pull/38">#2138</a>)</summary>
I added `graphql-ws` because there is no graphql-ws in package.json
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/f8e837b824ef4903a60f3cb974ef72fb4718a858"><tt>f8e837b8</tt></a> Use MultipartReader to parse file uploads (<a href="https://github.com/99designs/gqlgen/pull/35">#2135</a>)</summary>
Use a streaming MultipartReader to parse requests with file
uploads. The GraphQL multipart request specification guarantees
that the operations and map form fields will come first.
There are two reasons motivating this change:
- This allows for file uploads without specifying a specific
filename.
- This avoids unnecessary copies for requests with more than one
file. Go's ParseForm already copies the request's body into
memory or on disk. We were also doing this manually as a second
step.
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/05bfc1fb12f73648833e1055e775e074a6df7eed"><tt>05bfc1fb</tt></a> Upddate Changelog
- <a href="https://github.com/99designs/gqlgen/commit/62f694f0a8cf24f52dffca5823bb44fa1c32f97b"><tt>62f694f0</tt></a> v0.17.5 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.5"></a>
## [v0.17.5](https://github.com/99designs/gqlgen/compare/v0.17.4...v0.17.5) - 2022-04-29
- <a href="https://github.com/99designs/gqlgen/commit/fd97e74eafc898278fd4b74477cb053393672232"><tt>fd97e74e</tt></a> release v0.17.5
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/9250f9ac1f90b27da0bd8583ef8dcf0894d70686"><tt>9250f9ac</tt></a> Feature: Add FTV1 Support via Handler (<a href="https://github.com/99designs/gqlgen/pull/32">#2132</a>)</summary>
* initial support for ftv1 traces via handler
* remove testing json extension
* remove binary from commit and add to .gitignore
* updating go.mod
* updating examples go.sum
* rerunning generate within the examples folder
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/fce3a11a9f570ffed3e9035d32deddfb3076c2cf"><tt>fce3a11a</tt></a> feat: added graphql.UnmarshalInputFromContext (<a href="https://github.com/99designs/gqlgen/pull/31">#2131</a>)</summary>
* feat: added graphql.UnmarshalInputFromContext
* chore: run go generate for _examples
* fix: apply suggestions from code review
* fix: update error cases
* fix: fixed unit-test by update root_.gotpl
* fix: apply suggestions from code review
* fix: update graphql/input.go
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/6a24e88147fb2523af0163d7fa84d296b5e32e4d"><tt>6a24e881</tt></a> update instructions to specify package of Role (<a href="https://github.com/99designs/gqlgen/pull/30">#2130</a>)</summary>
Can't compile with the example unless I also include `model.` for Role.
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/ccfa245b1eb2657e588bf73f4df0e99f96869cbd"><tt>ccfa245b</tt></a> Ignore protobuf files in coverage (<a href="https://github.com/99designs/gqlgen/pull/33">#2133</a>)
- <a href="https://github.com/99designs/gqlgen/commit/0465dcb1e8e4177945c2670f15316ac96e4b992a"><tt>0465dcb1</tt></a> Update federation.md (<a href="https://github.com/99designs/gqlgen/pull/29">#2129</a>)
- <a href="https://github.com/99designs/gqlgen/commit/8f0631dcd3ca6fcfcd3dc6e69f4a92fec54e6dc7"><tt>8f0631dc</tt></a> Update Changelog
- <a href="https://github.com/99designs/gqlgen/commit/41611560d45f1226b860e795bcb35b5ecf09c5b3"><tt>41611560</tt></a> v0.17.4 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.4"></a>
## [v0.17.4](https://github.com/99designs/gqlgen/compare/v0.17.3...v0.17.4) - 2022-04-25
- <a href="https://github.com/99designs/gqlgen/commit/d6de831a28a0f1d8834c5dba4216dcd763814d3f"><tt>d6de831a</tt></a> release v0.17.4
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/2a2a3dcb67c7d713e41476eac47e20ab0e21fba7"><tt>2a2a3dcb</tt></a> Feature: Adds Federation 2 Support (<a href="https://github.com/99designs/gqlgen/pull/15">#2115</a>)</summary>
* fed2 rough support
* autodetection of fed2
* adding basic tests for changes
* fixing docs
* Update plugin/federation/federation.go
* removing custom scalar since it was causing issues
* fixing lint test
* should fix for real this time
* fixing test failures
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/77260e88c853a047e4e61a5357ceda4a5ea26405"><tt>77260e88</tt></a> shorten some generated code (<a href="https://github.com/99designs/gqlgen/pull/20">#2120</a>)</summary>
* shorten some generated code
* generate examples
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/4da17e1c7a59149eb6c2f5d60fcf11a2374b2488"><tt>4da17e1c</tt></a> update modules except mapstructure (<a href="https://github.com/99designs/gqlgen/pull/18">#2118</a>)</summary>
* Update modules
* Update modules except for mapstructure
* Try to update to v1.3.1
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/cddbf02d494e3aeaac3f60d1708b25facc5b767d"><tt>cddbf02d</tt></a> Update Changelog file
- <a href="https://github.com/99designs/gqlgen/commit/8f80f4efe8947b55919ce37291f4e908f57fd8dc"><tt>8f80f4ef</tt></a> v0.17.3 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.3"></a>
## [v0.17.3](https://github.com/99designs/gqlgen/compare/v0.17.2...v0.17.3) - 2022-04-20
- <a href="https://github.com/99designs/gqlgen/commit/0bb262d1a0143f60640f60ebbb516e0f4cd79042"><tt>0bb262d1</tt></a> release v0.17.3
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/8d0bd22aff1cdb6ad2e36190e11871b169f8da0a"><tt>8d0bd22a</tt></a> Update gqlparser (<a href="https://github.com/99designs/gqlgen/pull/09">#2109</a>)</summary>
* Update gqlparser
* Update tests to be NoError
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/ec0dea883a2c967d533e5f1530791ad72a08198b"><tt>ec0dea88</tt></a> Fix the ability of websockets to get errors (<a href="https://github.com/99designs/gqlgen/pull/97">#2097</a>)</summary>
Because DispatchOperation creates tempResponseContext,
which is passed into Exec, which is then used in _Subscription to
generate the next function. Inside the various subscription functions
when generating next the context was captured there.
Which means later when the returned function from DispatchOperation is
called. The responseContext which accumulates the errors is the
tempResponseContext which we no longer have access to to read the errors
out of it.
Instead add a context to next() so that it can be passed through and
accumulated the errors as expected.
Added a unit test for this as well.
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/e3f04b42f1fc5d4b13dc0579b2ec713f770a4fd0"><tt>e3f04b42</tt></a> Change the error message to be consumer targeted (<a href="https://github.com/99designs/gqlgen/pull/96">#2096</a>)</summary>
* Change the error message to be slightly more clear
* Rebase on updated origin/master.
Fix the test to not be sensitive to array ordering.
Re-generate on master as there was a schema change.
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/5a49764956ffb674df2c9bee19455bb1fd3407db"><tt>5a497649</tt></a> Fix websocket subscriptions to not double close. (<a href="https://github.com/99designs/gqlgen/pull/95">#2095</a>)</summary>
We were closing at the end of the loop and also in the defer.
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/a15a9bfdbad30b2f5ce7a966ec1190c108c4df3e"><tt>a15a9bfd</tt></a> Update test.yml to be valid
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/a1538928a569a09834579db941863ccce28113e3"><tt>a1538928</tt></a> Use Github API to update the docs (<a href="https://github.com/99designs/gqlgen/pull/01">#2101</a>)</summary>
* Use Github API to update the docs
Instead of a hard-coded version of the docs we want to realease, this
uses the Github API to get the last 20 versions and publish those. This
will allow any script invoking this to make sure to always have the
latest version of the docs
* Reinstate set -e
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/3bf437c232f8be30a473cf94495a1014c0583af2"><tt>3bf437c2</tt></a> Update golangci-lint (<a href="https://github.com/99designs/gqlgen/pull/03">#2103</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/12c6d0bf15431f666d08c4c82581957e1b727898"><tt>12c6d0bf</tt></a> Fix misprint (<a href="https://github.com/99designs/gqlgen/pull/02">#2102</a>)</summary>
* Fix misprint
* Update websocket_graphql_transport_ws.go
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/9f5fad13fa6275139e051788cc5fe8c2b2630428"><tt>9f5fad13</tt></a> Bump minimist from 1.2.5 to 1.2.6 in /integration (<a href="https://github.com/99designs/gqlgen/pull/85">#2085</a>)</summary>
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)
---
updated-dependencies:
- dependency-name: minimist
dependency-type: indirect
...
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/035e1d6eeb81179ddec3d36d8776212d8fe35cd6"><tt>035e1d6e</tt></a> Add AllowedMethods field to transport.Options (<a href="https://github.com/99designs/gqlgen/pull/80">#2080</a>)</summary>
* Add AllowedMethods field to transport.Options
to enable users to specify allowed HTTP methods.
* Update graphql/handler/transport/options.go
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/f0fdb116f45350aabf698c20bf6410283f96bb11"><tt>f0fdb116</tt></a> Add instructions for enabling autobinding (<a href="https://github.com/99designs/gqlgen/pull/79">#2079</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/12b0b38583e2c7b2174585bf1243a98cbbc2eba6"><tt>12b0b385</tt></a> Bump Playground version (<a href="https://github.com/99designs/gqlgen/pull/78">#2078</a>)</summary>
* update playground
* enables tabs
* update shas
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/1324c3ffb9ff0afef6e9cc41d99b5b4b9bc928b6"><tt>1324c3ff</tt></a> Merge pull request <a href="https://github.com/99designs/gqlgen/pull/62">#2062</a> from a8m/childfield</summary>
graphql: add FieldContext.Child field function and enable it in codegen
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/bf9caeaee091e32178fe2906894a7c7e72fdd66d"><tt>bf9caeae</tt></a> graphql: add FieldContext.ChildArgs field and enable it in codegen
- <a href="https://github.com/99designs/gqlgen/commit/36fb3dc6733601f96162bc80fccda42e34b3b7ff"><tt>36fb3dc6</tt></a> codegen: allow binding methods with optional variadic arguments (<a href="https://github.com/99designs/gqlgen/pull/66">#2066</a>)
- <a href="https://github.com/99designs/gqlgen/commit/fba5edd4fa1176ef0f2840f3bb90fe10b9f4b695"><tt>fba5edd4</tt></a> Update Changelog
- <a href="https://github.com/99designs/gqlgen/commit/48b2b7e1521c50d03cabf524bbb78805e3fb023f"><tt>48b2b7e1</tt></a> v0.17.2 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.2"></a>
## [v0.17.2](https://github.com/99designs/gqlgen/compare/v0.17.1...v0.17.2) - 2022-03-21
- <a href="https://github.com/99designs/gqlgen/commit/1f04d38a4441c5de6171400218b9dd25cebb3639"><tt>1f04d38a</tt></a> release v0.17.2
- <a href="https://github.com/99designs/gqlgen/commit/87fc5f22e8fbfa28a180cbf0e7008af9f830273e"><tt>87fc5f22</tt></a> Fix <a href="https://github.com/99designs/gqlgen/pull/52">#1961](https://github.com/99designs/gqlgen/issues/1961) for Go 1.18 ([#2052</a>)
- <a href="https://github.com/99designs/gqlgen/commit/f85d59d30ae055fd89b79aa3d7e3ca1c7fcaedfa"><tt>f85d59d3</tt></a> fixed modelgen test schema (<a href="https://github.com/99designs/gqlgen/pull/32">#2032</a>)
- <a href="https://github.com/99designs/gqlgen/commit/d873ff8bb9927b302752bd48d7836f2597db558e"><tt>d873ff8b</tt></a> v0.17.1 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.1"></a> <a name="v0.17.1"></a>
## [v0.17.1](https://github.com/99designs/gqlgen/compare/v0.17.0...v0.17.1) - 2022-03-02 ## [v0.17.1](https://github.com/99designs/gqlgen/compare/v0.17.0...v0.17.1) - 2022-03-02
- <a href="https://github.com/99designs/gqlgen/commit/5ea50aee16088ed414be73ca9a59a90f622c9483"><tt>5ea50aee</tt></a> release v0.17.1
- <a href="https://github.com/99designs/gqlgen/commit/a493a4239673c5922281628fc8b94c727398283e"><tt>a493a423</tt></a> Prepare for new release
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/9f520a2897cf42750e7290cbd83de6fdf13f2e75"><tt>9f520a28</tt></a> Update golangci-lint and fix resource leak (<a href="https://github.com/99designs/gqlgen/pull/24">#2024</a>)</summary> <dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/9f520a2897cf42750e7290cbd83de6fdf13f2e75"><tt>9f520a28</tt></a> Update golangci-lint and fix resource leak (<a href="https://github.com/99designs/gqlgen/pull/24">#2024</a>)</summary>
* Fix golangci-lint in CI * Fix golangci-lint in CI

View file

@ -2,6 +2,7 @@ package api
import ( import (
"fmt" "fmt"
"regexp"
"syscall" "syscall"
"github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen"
@ -24,7 +25,20 @@ func Generate(cfg *config.Config, option ...Option) error {
} }
plugins = append(plugins, resolvergen.New()) plugins = append(plugins, resolvergen.New())
if cfg.Federation.IsDefined() { if cfg.Federation.IsDefined() {
plugins = append([]plugin.Plugin{federation.New()}, plugins...) if cfg.Federation.Version == 0 { // default to using the user's choice of version, but if unset, try to sort out which federation version to use
urlRegex := regexp.MustCompile(`(?s)@link.*\(.*url:.*?"(.*?)"[^)]+\)`) // regex to grab the url of a link directive, should it exist
// check the sources, and if one is marked as federation v2, we mark the entirety to be generated using that format
for _, v := range cfg.Sources {
cfg.Federation.Version = 1
urlString := urlRegex.FindStringSubmatch(v.Input)
if urlString != nil && urlString[1] == "https://specs.apollo.dev/federation/v2.0" {
cfg.Federation.Version = 2
break
}
}
}
plugins = append([]plugin.Plugin{federation.New(cfg.Federation.Version)}, plugins...)
} }
for _, o := range option { for _, o := range option {

View file

@ -73,11 +73,15 @@ func (b *builder) buildArg(obj *Object, arg *ast.ArgumentDefinition) (*FieldArgu
return &newArg, nil return &newArg, nil
} }
func (b *builder) bindArgs(field *Field, params *types.Tuple) ([]*FieldArgument, error) { func (b *builder) bindArgs(field *Field, sig *types.Signature, params *types.Tuple) ([]*FieldArgument, error) {
var newArgs []*FieldArgument n := params.Len()
newArgs := make([]*FieldArgument, 0, len(field.Args))
// Accept variadic methods (i.e. have optional parameters).
if params.Len() > len(field.Args) && sig.Variadic() {
n = len(field.Args)
}
nextArg: nextArg:
for j := 0; j < params.Len(); j++ { for j := 0; j < n; j++ {
param := params.At(j) param := params.At(j)
for _, oldArg := range field.Args { for _, oldArg := range field.Args {
if strings.EqualFold(oldArg.Name, param.Name()) { if strings.EqualFold(oldArg.Name, param.Name()) {

View file

@ -12,6 +12,7 @@ import (
type PackageConfig struct { type PackageConfig struct {
Filename string `yaml:"filename,omitempty"` Filename string `yaml:"filename,omitempty"`
Package string `yaml:"package,omitempty"` Package string `yaml:"package,omitempty"`
Version int `yaml:"version,omitempty"`
} }
func (c *PackageConfig) ImportPath() string { func (c *PackageConfig) ImportPath() string {

View file

@ -2,7 +2,10 @@ package codegen
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"sort" "sort"
"strings"
"github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/ast"
@ -30,6 +33,26 @@ type Data struct {
QueryRoot *Object QueryRoot *Object
MutationRoot *Object MutationRoot *Object
SubscriptionRoot *Object SubscriptionRoot *Object
AugmentedSources []AugmentedSource
}
func (d *Data) HasEmbeddableSources() bool {
hasEmbeddableSources := false
for _, s := range d.AugmentedSources {
if s.Embeddable {
hasEmbeddableSources = true
}
}
return hasEmbeddableSources
}
// AugmentedSource contains extra information about graphql schema files which is not known directly from the Config.Sources data
type AugmentedSource struct {
// path relative to Config.Exec.Filename
RelativePath string
Embeddable bool
BuiltIn bool
Source string
} }
type builder struct { type builder struct {
@ -147,6 +170,31 @@ func BuildData(cfg *config.Config) (*Data, error) {
// otherwise show a generic error message // otherwise show a generic error message
return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug") return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug")
} }
aSources := []AugmentedSource{}
for _, s := range cfg.Sources {
wd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get working directory: %w", err)
}
outputDir := cfg.Exec.Dir()
sourcePath := filepath.Join(wd, s.Name)
relative, err := filepath.Rel(outputDir, sourcePath)
if err != nil {
return nil, fmt.Errorf("failed to compute path of %s relative to %s: %w", sourcePath, outputDir, err)
}
relative = filepath.ToSlash(relative)
embeddable := true
if strings.HasPrefix(relative, "..") || s.BuiltIn {
embeddable = false
}
aSources = append(aSources, AugmentedSource{
RelativePath: relative,
Embeddable: embeddable,
BuiltIn: s.BuiltIn,
Source: s.Input,
})
}
s.AugmentedSources = aSources
return &s, nil return &s, nil
} }

View file

@ -70,7 +70,7 @@ func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.Op
{{ end }} {{ end }}
{{ if .Directives.LocationDirectives "SUBSCRIPTION" }} {{ if .Directives.LocationDirectives "SUBSCRIPTION" }}
func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func() graphql.Marshaler { func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func(ctx context.Context) graphql.Marshaler {
for _, d := range obj.Directives { for _, d := range obj.Directives {
switch d.Name { switch d.Name {
{{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }} {{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }}
@ -80,7 +80,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs) args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
return func() graphql.Marshaler { return func(ctx context.Context) graphql.Marshaler {
return graphql.Null return graphql.Null
} }
} }
@ -98,15 +98,15 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
tmp, err := next(ctx) tmp, err := next(ctx)
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
return func() graphql.Marshaler { return func(ctx context.Context) graphql.Marshaler {
return graphql.Null return graphql.Null
} }
} }
if data, ok := tmp.(func() graphql.Marshaler); ok { if data, ok := tmp.(func(ctx context.Context) graphql.Marshaler); ok {
return data return data
} }
ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp) ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp)
return func() graphql.Marshaler { return func(ctx context.Context) graphql.Marshaler {
return graphql.Null return graphql.Null
} }
} }

View file

@ -179,8 +179,8 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) {
params = types.NewTuple(vars...) params = types.NewTuple(vars...)
} }
// Try to match target function's arguments with GraphQL field arguments // Try to match target function's arguments with GraphQL field arguments.
newArgs, err := b.bindArgs(f, params) newArgs, err := b.bindArgs(f, sig, params)
if err != nil { if err != nil {
return fmt.Errorf("%s:%d: %w", pos.Filename, pos.Line, err) return fmt.Errorf("%s:%d: %w", pos.Filename, pos.Line, err)
} }
@ -483,6 +483,14 @@ func (f *Field) ArgsFunc() string {
return "field_" + f.Object.Definition.Name + "_" + f.Name + "_args" return "field_" + f.Object.Definition.Name + "_" + f.Name + "_args"
} }
func (f *Field) FieldContextFunc() string {
return "fieldContext_" + f.Object.Definition.Name + "_" + f.Name
}
func (f *Field) ChildFieldContextFunc(name string) string {
return "fieldContext_" + f.TypeReference.Definition.Name + "_" + name
}
func (f *Field) ResolverType() string { func (f *Field) ResolverType() string {
if !f.IsResolver { if !f.IsResolver {
return "" return ""
@ -549,7 +557,7 @@ func (f *Field) CallArgs() string {
} }
for _, arg := range f.Args { for _, arg := range f.Args {
args = append(args, "args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")") args = append(args, "fc.Args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")")
} }
return strings.Join(args, ", ") return strings.Join(args, ", ")

View file

@ -1,34 +1,21 @@
{{- range $object := .Objects }}{{- range $field := $object.Fields }} {{- range $object := .Objects }}{{- range $field := $object.Fields }}
func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret {{ if $object.Stream }}func(){{ end }}graphql.Marshaler) { func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret {{ if $object.Stream }}func(ctx context.Context){{ end }}graphql.Marshaler) {
{{- $null := "graphql.Null" }} {{- $null := "graphql.Null" }}
{{- if $object.Stream }} {{- if $object.Stream }}
{{- $null = "nil" }} {{- $null = "nil" }}
{{- end }} {{- end }}
fc, err := ec.{{ $field.FieldContextFunc }}(ctx, field)
if err != nil {
return {{ $null }}
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func () { defer func () {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
ret = {{ $null }} ret = {{ $null }}
} }
}() }()
fc := &graphql.FieldContext{
Object: {{$object.Name|quote}},
Field: field,
Args: nil,
IsMethod: {{or $field.IsMethod $field.IsResolver}},
IsResolver: {{ $field.IsResolver }},
}
ctx = graphql.WithFieldContext(ctx, fc)
{{- if $field.Args }}
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs)
if err != nil {
ec.Error(ctx, err)
return {{ $null }}
}
fc.Args = args
{{- end }}
{{- if $.AllDirectives.LocationDirectives "FIELD" }} {{- if $.AllDirectives.LocationDirectives "FIELD" }}
resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) { resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
{{ template "field" $field }} {{ template "field" $field }}
@ -51,8 +38,9 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
return {{ $null }} return {{ $null }}
} }
{{- if $object.Stream }} {{- if $object.Stream }}
return func() graphql.Marshaler { return func(ctx context.Context) graphql.Marshaler {
res, ok := <-resTmp.(<-chan {{$field.TypeReference.GO | ref}}) select {
case res, ok := <-resTmp.(<-chan {{$field.TypeReference.GO | ref}}):
if !ok { if !ok {
return nil return nil
} }
@ -63,6 +51,9 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res).MarshalGQL(w) ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res).MarshalGQL(w)
w.Write([]byte{'}'}) w.Write([]byte{'}'})
}) })
case <-ctx.Done():
return nil
}
} }
{{- else }} {{- else }}
res := resTmp.({{$field.TypeReference.GO | ref}}) res := resTmp.({{$field.TypeReference.GO | ref}})
@ -71,6 +62,44 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
{{- end }} {{- end }}
} }
func (ec *executionContext) {{ $field.FieldContextFunc }}(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: {{quote $field.Object.Name}},
Field: field,
IsMethod: {{or $field.IsMethod $field.IsResolver}},
IsResolver: {{ $field.IsResolver }},
Child: func (ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
{{- if not $field.TypeReference.Definition.Fields }}
return nil, errors.New("field of type {{ $field.TypeReference.Definition.Name }} does not have child fields")
{{- else if ne $field.TypeReference.Definition.Kind "OBJECT" }}
return nil, errors.New("FieldContext.Child cannot be called on type {{ $field.TypeReference.Definition.Kind }}")
{{- else }}
switch field.Name {
{{- range $f := $field.TypeReference.Definition.Fields }}
case "{{ $f.Name }}":
return ec.{{ $field.ChildFieldContextFunc $f.Name }}(ctx, field)
{{- end }}
}
return nil, fmt.Errorf("no field named %q was found under type {{ $field.TypeReference.Definition.Name }}", field.Name)
{{- end }}
},
}
{{- if $field.Args }}
defer func () {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.{{ $field.ArgsFunc }}(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
{{- end }}
return fc, nil
}
{{- end }}{{- end}} {{- end }}{{- end}}
{{ define "field" }} {{ define "field" }}

View file

@ -7,6 +7,7 @@
{{ reserveImport "sync/atomic" }} {{ reserveImport "sync/atomic" }}
{{ reserveImport "errors" }} {{ reserveImport "errors" }}
{{ reserveImport "bytes" }} {{ reserveImport "bytes" }}
{{ reserveImport "embed" }}
{{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }} {{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} {{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
@ -135,6 +136,13 @@
func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
rc := graphql.GetOperationContext(ctx) rc := graphql.GetOperationContext(ctx)
ec := executionContext{rc, e} ec := executionContext{rc, e}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
{{- range $input := .Inputs -}}
{{ if not $input.HasUnmarshal }}
ec.unmarshalInput{{ $input.Name }},
{{- end }}
{{- end }}
)
first := true first := true
switch rc.Operation.Operation { switch rc.Operation.Operation {
@ -142,6 +150,7 @@
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
if !first { return nil } if !first { return nil }
first = false first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "QUERY" -}} {{ if .Directives.LocationDirectives "QUERY" -}}
data := ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ data := ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
@ -162,6 +171,7 @@
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
if !first { return nil } if !first { return nil }
first = false first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "MUTATION" -}} {{ if .Directives.LocationDirectives "MUTATION" -}}
data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
@ -190,7 +200,7 @@
var buf bytes.Buffer var buf bytes.Buffer
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
buf.Reset() buf.Reset()
data := next() data := next(ctx)
if data == nil { if data == nil {
return nil return nil
@ -226,9 +236,22 @@
return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil
} }
{{if .HasEmbeddableSources }}
//go:embed{{- range $source := .AugmentedSources }}{{if $source.Embeddable}} {{$source.RelativePath|quote}}{{end}}{{- end }}
var sourcesFS embed.FS
func sourceData(filename string) string {
data, err := sourcesFS.ReadFile(filename)
if err != nil {
panic(fmt.Sprintf("codegen problem: %s not available", filename))
}
return string(data)
}
{{- end }}
var sources = []*ast.Source{ var sources = []*ast.Source{
{{- range $source := .Config.Sources }} {{- range $source := .AugmentedSources }}
{Name: {{$source.Name|quote}}, Input: {{$source.Input|rawQuote}}, BuiltIn: {{$source.BuiltIn}}}, {Name: {{$source.RelativePath|quote}}, Input: {{if (not $source.Embeddable)}}{{$source.Source|rawQuote}}{{else}}sourceData({{$source.RelativePath|quote}}){{end}}, BuiltIn: {{$source.BuiltIn}}},
{{- end }} {{- end }}
} }
var parsedSchema = gqlparser.MustLoadSchema(sources...) var parsedSchema = gqlparser.MustLoadSchema(sources...)

View file

@ -3,7 +3,7 @@
var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}} var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}}
{{- if .Stream }} {{- if .Stream }}
func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler { func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func(ctx context.Context) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors) fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors)
ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{
Object: {{$object.Name|quote}}, Object: {{$object.Name|quote}},
@ -80,13 +80,12 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
{{end}} {{end}}
}) })
{{- else }} {{- else }}
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
return ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}})
}
{{if $object.Root}} {{if $object.Root}}
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, innerFunc) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._{{$object.Name}}_{{$field.Name}}(ctx, field)
})
{{else}} {{else}}
out.Values[i] = innerFunc(ctx) out.Values[i] = ec._{{$object.Name}}_{{$field.Name}}(ctx, field, obj)
{{end}} {{end}}
{{- if $field.TypeReference.GQL.NonNull }} {{- if $field.TypeReference.GQL.NonNull }}

View file

@ -7,6 +7,7 @@
{{ reserveImport "sync/atomic" }} {{ reserveImport "sync/atomic" }}
{{ reserveImport "errors" }} {{ reserveImport "errors" }}
{{ reserveImport "bytes" }} {{ reserveImport "bytes" }}
{{ reserveImport "embed" }}
{{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }} {{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} {{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
@ -102,6 +103,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
rc := graphql.GetOperationContext(ctx) rc := graphql.GetOperationContext(ctx)
ec := executionContext{rc, e} ec := executionContext{rc, e}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
{{- range $input := .Inputs -}}
{{ if not $input.HasUnmarshal }}
ec.unmarshalInput{{ $input.Name }},
{{- end }}
{{- end }}
)
first := true first := true
switch rc.Operation.Operation { switch rc.Operation.Operation {
@ -109,6 +117,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
if !first { return nil } if !first { return nil }
first = false first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "QUERY" -}} {{ if .Directives.LocationDirectives "QUERY" -}}
data := ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ data := ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
@ -129,6 +138,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
if !first { return nil } if !first { return nil }
first = false first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "MUTATION" -}} {{ if .Directives.LocationDirectives "MUTATION" -}}
data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
@ -157,7 +167,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
var buf bytes.Buffer var buf bytes.Buffer
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
buf.Reset() buf.Reset()
data := next() data := next(ctx)
if data == nil { if data == nil {
return nil return nil
@ -193,9 +203,23 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil
} }
{{if .HasEmbeddableSources }}
//go:embed{{- range $source := .AugmentedSources }}{{if $source.Embeddable}} {{$source.RelativePath|quote}}{{end}}{{- end }}
var sourcesFS embed.FS
func sourceData(filename string) string {
data, err := sourcesFS.ReadFile(filename)
if err != nil {
panic(fmt.Sprintf("codegen problem: %s not available", filename))
}
return string(data)
}
{{- end}}
var sources = []*ast.Source{ var sources = []*ast.Source{
{{- range $source := .Config.Sources }} {{- range $source := .AugmentedSources }}
{Name: {{$source.Name|quote}}, Input: {{$source.Input|rawQuote}}, BuiltIn: {{$source.BuiltIn}}}, {Name: {{$source.RelativePath|quote}}, Input: {{if (not $source.Embeddable)}}{{$source.Source|rawQuote}}{{else}}sourceData({{$source.RelativePath|quote}}){{end}}, BuiltIn: {{$source.BuiltIn}}},
{{- end }} {{- end }}
} }
var parsedSchema = gqlparser.MustLoadSchema(sources...) var parsedSchema = gqlparser.MustLoadSchema(sources...)

View file

@ -151,7 +151,7 @@
if v == nil { if v == nil {
{{- if $type.GQL.NonNull }} {{- if $type.GQL.NonNull }}
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null") ec.Errorf(ctx, "the requested element is null which the schema does not allow")
} }
{{- end }} {{- end }}
return graphql.Null return graphql.Null
@ -174,7 +174,7 @@
{{- if $type.GQL.NonNull }} {{- if $type.GQL.NonNull }}
if res == graphql.Null { if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null") ec.Errorf(ctx, "the requested element is null which the schema does not allow")
} }
} }
{{- end }} {{- end }}

View file

@ -3,22 +3,20 @@ module github.com/99designs/gqlgen
go 1.16 go 1.16
require ( require (
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.0 github.com/hashicorp/golang-lru v0.5.4
github.com/kevinmbeaulieu/eq-go v1.0.0 github.com/kevinmbeaulieu/eq-go v1.0.0
github.com/logrusorgru/aurora/v3 v3.0.0 github.com/logrusorgru/aurora/v3 v3.0.0
github.com/matryer/moq v0.2.3 github.com/matryer/moq v0.2.7
github.com/mattn/go-colorable v0.1.4 github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-isatty v0.0.14
github.com/mitchellh/mapstructure v1.2.3 github.com/mitchellh/mapstructure v1.3.1
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.7.1
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.8.1
github.com/vektah/gqlparser/v2 v2.4.0 github.com/vektah/gqlparser/v2 v2.4.4
golang.org/x/tools v0.1.9 golang.org/x/tools v0.1.10
gopkg.in/yaml.v2 v2.2.8 google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
) )
require ( require github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/agnivade/levenshtein v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
)

View file

@ -1,12 +1,11 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -14,10 +13,13 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo= github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo=
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -27,73 +29,77 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/matryer/moq v0.2.3 h1:Q06vEqnBYjjfx5KKgHfYRKE/lvlRu+Nj+xodG4YdHnU= github.com/matryer/moq v0.2.7 h1:RtpiPUM8L7ZSCbSwK+QcZH/E9tgqAkFjKQxsRs25b4w=
github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vektah/gqlparser/v2 v2.4.0 h1:EmA4dw9mqHm0j6Xzb9T21hOrp3oXmxnS40vwki70DZU= github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/vektah/gqlparser/v2 v2.4.0/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/vektah/gqlparser/v2 v2.4.4 h1:rh9hwZ5Jx9cCq88zXz2YHKmuQBuwY1JErHU8GywFdwE=
github.com/vektah/gqlparser/v2 v2.4.4/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -30,6 +30,25 @@ type FieldContext struct {
IsMethod bool IsMethod bool
// IsResolver indicates if the field has a user-specified resolver // IsResolver indicates if the field has a user-specified resolver
IsResolver bool IsResolver bool
// Child allows getting a child FieldContext by its field collection description.
// Note that, the returned child FieldContext represents the context as it was
// before the execution of the field resolver. For example:
//
// srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (interface{}, error) {
// fc := graphql.GetFieldContext(ctx)
// op := graphql.GetOperationContext(ctx)
// collected := graphql.CollectFields(opCtx, fc.Field.Selections, []string{"User"})
//
// child, err := fc.Child(ctx, collected[0])
// if err != nil {
// return nil, err
// }
// fmt.Println("child context %q with args: %v", child.Field.Name, child.Args)
//
// return next(ctx)
// })
//
Child func(context.Context, CollectedField) (*FieldContext, error)
} }
type FieldStats struct { type FieldStats struct {

View file

@ -67,7 +67,9 @@ func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.R
rc.Operation = rc.Doc.Operations.ForName(params.OperationName) rc.Operation = rc.Doc.Operations.ForName(params.OperationName)
if rc.Operation == nil { if rc.Operation == nil {
return rc, gqlerror.List{gqlerror.Errorf("operation %s not found", params.OperationName)} err := gqlerror.Errorf("operation %s not found", params.OperationName)
errcode.Set(err, errcode.ValidationFailed)
return rc, gqlerror.List{err}
} }
var err *gqlerror.Error var err *gqlerror.Error
@ -178,6 +180,13 @@ func (e *Executor) parseQuery(ctx context.Context, stats *graphql.Stats, query s
stats.Parsing.End = graphql.Now() stats.Parsing.End = graphql.Now()
stats.Validation.Start = graphql.Now() stats.Validation.Start = graphql.Now()
if len(doc.Operations) == 0 {
err = gqlerror.Errorf("no operation provided")
errcode.Set(err, errcode.ValidationFailed)
return nil, gqlerror.List{err}
}
listErr := validator.Validate(e.es.Schema(), doc) listErr := validator.Validate(e.es.Schema(), doc)
if len(listErr) != 0 { if len(listErr) != 0 {
for _, e := range listErr { for _, e := range listErr {

View file

@ -27,6 +27,7 @@ type (
OperationName string `json:"operationName"` OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"` Variables map[string]interface{} `json:"variables"`
Extensions map[string]interface{} `json:"extensions"` Extensions map[string]interface{} `json:"extensions"`
Headers http.Header `json:"headers"`
ReadTime TraceTiming `json:"-"` ReadTime TraceTiming `json:"-"`
} }

View file

@ -7,7 +7,6 @@ import (
"mime" "mime"
"net/http" "net/http"
"os" "os"
"strings"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
) )
@ -64,63 +63,68 @@ func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.G
return return
} }
r.Body = http.MaxBytesReader(w, r.Body, f.maxUploadSize()) r.Body = http.MaxBytesReader(w, r.Body, f.maxUploadSize())
if err = r.ParseMultipartForm(f.maxMemory()); err != nil { defer r.Body.Close()
mr, err := r.MultipartReader()
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
if strings.Contains(err.Error(), "request body too large") {
writeJsonError(w, "failed to parse multipart form, request body too large")
return
}
writeJsonError(w, "failed to parse multipart form") writeJsonError(w, "failed to parse multipart form")
return return
} }
defer r.Body.Close()
part, err := mr.NextPart()
if err != nil || part.FormName() != "operations" {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonError(w, "first part must be operations")
return
}
var params graphql.RawParams var params graphql.RawParams
if err = jsonDecode(part, &params); err != nil {
if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), &params); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonError(w, "operations form field could not be decoded") writeJsonError(w, "operations form field could not be decoded")
return return
} }
part, err = mr.NextPart()
if err != nil || part.FormName() != "map" {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonError(w, "second part must be map")
return
}
uploadsMap := map[string][]string{} uploadsMap := map[string][]string{}
if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { if err = json.NewDecoder(part).Decode(&uploadsMap); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonError(w, "map form field could not be decoded") writeJsonError(w, "map form field could not be decoded")
return return
} }
var upload graphql.Upload for {
for key, paths := range uploadsMap { part, err = mr.NextPart()
if err == io.EOF {
break
} else if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonErrorf(w, "failed to parse part")
return
}
key := part.FormName()
filename := part.FileName()
contentType := part.Header.Get("Content-Type")
paths := uploadsMap[key]
if len(paths) == 0 { if len(paths) == 0 {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonErrorf(w, "invalid empty operations paths list for key %s", key) writeJsonErrorf(w, "invalid empty operations paths list for key %s", key)
return return
} }
file, header, err := r.FormFile(key) delete(uploadsMap, key)
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonErrorf(w, "failed to get key %s from form", key)
return
}
defer file.Close()
if len(paths) == 1 { var upload graphql.Upload
upload = graphql.Upload{
File: file,
Size: header.Size,
Filename: header.Filename,
ContentType: header.Header.Get("Content-Type"),
}
if err := params.AddUpload(upload, key, paths[0]); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonGraphqlError(w, err)
return
}
} else {
if r.ContentLength < f.maxMemory() { if r.ContentLength < f.maxMemory() {
fileBytes, err := ioutil.ReadAll(file) fileBytes, err := ioutil.ReadAll(part)
if err != nil { if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonErrorf(w, "failed to read file for key %s", key) writeJsonErrorf(w, "failed to read file for key %s", key)
@ -129,9 +133,9 @@ func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.G
for _, path := range paths { for _, path := range paths {
upload = graphql.Upload{ upload = graphql.Upload{
File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1},
Size: header.Size, Size: int64(len(fileBytes)),
Filename: header.Filename, Filename: filename,
ContentType: header.Header.Get("Content-Type"), ContentType: contentType,
} }
if err := params.AddUpload(upload, key, path); err != nil { if err := params.AddUpload(upload, key, path); err != nil {
@ -151,7 +155,7 @@ func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.G
defer func() { defer func() {
_ = os.Remove(tmpName) _ = os.Remove(tmpName)
}() }()
_, err = io.Copy(tmpFile, file) fileSize, err := io.Copy(tmpFile, part)
if err != nil { if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
if err := tmpFile.Close(); err != nil { if err := tmpFile.Close(); err != nil {
@ -176,9 +180,9 @@ func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.G
defer pathTmpFile.Close() defer pathTmpFile.Close()
upload = graphql.Upload{ upload = graphql.Upload{
File: pathTmpFile, File: pathTmpFile,
Size: header.Size, Size: fileSize,
Filename: header.Filename, Filename: filename,
ContentType: header.Header.Get("Content-Type"), ContentType: contentType,
} }
if err := params.AddUpload(upload, key, path); err != nil { if err := params.AddUpload(upload, key, path); err != nil {
@ -189,8 +193,15 @@ func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.G
} }
} }
} }
for key := range uploadsMap {
w.WriteHeader(http.StatusUnprocessableEntity)
writeJsonErrorf(w, "failed to get key %s from form", key)
return
} }
params.Headers = r.Header
params.ReadTime = graphql.TraceTiming{ params.ReadTime = graphql.TraceTiming{
Start: start, Start: start,
End: graphql.Now(), End: graphql.Now(),

View file

@ -32,6 +32,7 @@ func (h GET) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecut
raw := &graphql.RawParams{ raw := &graphql.RawParams{
Query: r.URL.Query().Get("query"), Query: r.URL.Query().Get("query"),
OperationName: r.URL.Query().Get("operationName"), OperationName: r.URL.Query().Get("operationName"),
Headers: r.Header,
} }
raw.ReadTime.Start = graphql.Now() raw.ReadTime.Start = graphql.Now()

View file

@ -36,6 +36,9 @@ func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecu
writeJsonErrorf(w, "json body could not be decoded: "+err.Error()) writeJsonErrorf(w, "json body could not be decoded: "+err.Error())
return return
} }
params.Headers = r.Header
params.ReadTime = graphql.TraceTiming{ params.ReadTime = graphql.TraceTiming{
Start: start, Start: start,
End: graphql.Now(), End: graphql.Now(),

View file

@ -2,12 +2,16 @@ package transport
import ( import (
"net/http" "net/http"
"strings"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
) )
// Options responds to http OPTIONS and HEAD requests // Options responds to http OPTIONS and HEAD requests
type Options struct{} type Options struct {
// AllowedMethods is a list of allowed HTTP methods.
AllowedMethods []string
}
var _ graphql.Transport = Options{} var _ graphql.Transport = Options{}
@ -18,9 +22,16 @@ func (o Options) Supports(r *http.Request) bool {
func (o Options) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { func (o Options) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) {
switch r.Method { switch r.Method {
case http.MethodOptions: case http.MethodOptions:
w.Header().Set("Allow", "OPTIONS, GET, POST") w.Header().Set("Allow", o.allowedMethods())
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
case http.MethodHead: case http.MethodHead:
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
} }
func (o Options) allowedMethods() string {
if len(o.AllowedMethods) == 0 {
return "OPTIONS, GET, POST"
}
return strings.Join(o.AllowedMethods, ", ")
}

View file

@ -49,7 +49,24 @@ type (
var errReadTimeout = errors.New("read timeout") var errReadTimeout = errors.New("read timeout")
var _ graphql.Transport = Websocket{} type WebsocketError struct {
Err error
// IsReadError flags whether the error occurred on read or write to the websocket
IsReadError bool
}
func (e WebsocketError) Error() string {
if e.IsReadError {
return fmt.Sprintf("websocket read: %v", e.Err)
}
return fmt.Sprintf("websocket write: %v", e.Err)
}
var (
_ graphql.Transport = Websocket{}
_ error = WebsocketError{}
)
func (t Websocket) Supports(r *http.Request) bool { func (t Websocket) Supports(r *http.Request) bool {
return r.Header.Get("Upgrade") != "" return r.Header.Get("Upgrade") != ""
@ -94,9 +111,12 @@ func (t Websocket) Do(w http.ResponseWriter, r *http.Request, exec graphql.Graph
conn.run() conn.run()
} }
func (c *wsConnection) handlePossibleError(err error) { func (c *wsConnection) handlePossibleError(err error, isReadError bool) {
if c.ErrorFunc != nil && err != nil { if c.ErrorFunc != nil && err != nil {
c.ErrorFunc(c.ctx, err) c.ErrorFunc(c.ctx, WebsocketError{
Err: err,
IsReadError: isReadError,
})
} }
} }
@ -181,7 +201,7 @@ func (c *wsConnection) init() bool {
func (c *wsConnection) write(msg *message) { func (c *wsConnection) write(msg *message) {
c.mu.Lock() c.mu.Lock()
c.handlePossibleError(c.me.Send(msg)) c.handlePossibleError(c.me.Send(msg), false)
c.mu.Unlock() c.mu.Unlock()
} }
@ -227,7 +247,7 @@ func (c *wsConnection) run() {
if err != nil { if err != nil {
// If the connection got closed by us, don't report the error // If the connection got closed by us, don't report the error
if !errors.Is(err, net.ErrClosed) { if !errors.Is(err, net.ErrClosed) {
c.handlePossibleError(err) c.handlePossibleError(err, true)
} }
return return
} }
@ -358,12 +378,8 @@ func (c *wsConnection) subscribe(start time.Time, msg *message) {
c.sendResponse(msg.id, response) c.sendResponse(msg.id, response)
} }
c.complete(msg.id)
c.mu.Lock() // complete and context cancel comes from the defer
delete(c.active, msg.id)
c.mu.Unlock()
cancel()
}() }()
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md // https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md
const ( const (
graphqltransportwsSubprotocol = "graphql-transport-ws" graphqltransportwsSubprotocol = "graphql-transport-ws"

View file

@ -7,7 +7,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md // https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
const ( const (
graphqlwsSubprotocol = "graphql-ws" graphqlwsSubprotocol = "graphql-ws"

55
vendor/github.com/99designs/gqlgen/graphql/input.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
package graphql
import (
"context"
"errors"
"reflect"
)
const unmarshalInputCtx key = "unmarshal_input_context"
// BuildUnmarshalerMap returns a map of unmarshal functions of the ExecutableContext
// to use with the WithUnmarshalerMap function.
func BuildUnmarshalerMap(unmarshaler ...interface{}) map[reflect.Type]reflect.Value {
maps := make(map[reflect.Type]reflect.Value)
for _, v := range unmarshaler {
ft := reflect.TypeOf(v)
if ft.Kind() == reflect.Func {
maps[ft.Out(0)] = reflect.ValueOf(v)
}
}
return maps
}
// WithUnmarshalerMap returns a new context with a map from input types to their unmarshaler functions.
func WithUnmarshalerMap(ctx context.Context, maps map[reflect.Type]reflect.Value) context.Context {
return context.WithValue(ctx, unmarshalInputCtx, maps)
}
// UnmarshalInputFromContext allows unmarshaling input object from a context.
func UnmarshalInputFromContext(ctx context.Context, raw, v interface{}) error {
m, ok := ctx.Value(unmarshalInputCtx).(map[reflect.Type]reflect.Value)
if m == nil || !ok {
return errors.New("graphql: the input context is empty")
}
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("graphql: input must be a non-nil pointer")
}
if fn, ok := m[rv.Elem().Type()]; ok {
res := fn.Call([]reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(raw),
})
if err := res[1].Interface(); err != nil {
return err.(error)
}
rv.Elem().Set(res[0])
return nil
}
return errors.New("graphql: no unmarshal function found")
}

View file

@ -3,6 +3,7 @@ package playground
import ( import (
"html/template" "html/template"
"net/http" "net/http"
"net/url"
) )
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html> var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
@ -36,14 +37,20 @@ var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
></script> ></script>
<script> <script>
const url = location.protocol + '//' + location.host + '{{.endpoint}}'; {{- if .endpointIsAbsolute}}
const url = {{.endpoint}};
const subscriptionUrl = {{.subscriptionEndpoint}};
{{- else}}
const url = location.protocol + '//' + location.host + {{.endpoint}};
const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:'; const wsProto = location.protocol == 'https:' ? 'wss:' : 'ws:';
const subscriptionUrl = wsProto + '//' + location.host + '{{.endpoint}}'; const subscriptionUrl = wsProto + '//' + location.host + {{.endpoint}};
{{- end}}
const fetcher = GraphiQL.createFetcher({ url, subscriptionUrl }); const fetcher = GraphiQL.createFetcher({ url, subscriptionUrl });
ReactDOM.render( ReactDOM.render(
React.createElement(GraphiQL, { React.createElement(GraphiQL, {
fetcher: fetcher, fetcher: fetcher,
tabs: true,
headerEditorEnabled: true, headerEditorEnabled: true,
shouldPersistHeaders: true shouldPersistHeaders: true
}), }),
@ -54,15 +61,18 @@ var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
</html> </html>
`)) `))
// Handler responsible for setting up the playground
func Handler(title string, endpoint string) http.HandlerFunc { func Handler(title string, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html")
err := page.Execute(w, map[string]string{ err := page.Execute(w, map[string]interface{}{
"title": title, "title": title,
"endpoint": endpoint, "endpoint": endpoint,
"version": "1.5.16", "endpointIsAbsolute": endpointHasScheme(endpoint),
"cssSRI": "sha256-HADQowUuFum02+Ckkv5Yu5ygRoLllHZqg0TFZXY7NHI=", "subscriptionEndpoint": getSubscriptionEndpoint(endpoint),
"jsSRI": "sha256-uHp12yvpXC4PC9+6JmITxKuLYwjlW9crq9ywPE5Rxco=", "version": "1.8.2",
"cssSRI": "sha256-CDHiHbYkDSUc3+DS2TU89I9e2W3sJRUOqSmp7JC+LBw=",
"jsSRI": "sha256-X8vqrqZ6Rvvoq4tvRVM3LoMZCQH8jwW92tnX0iPiHPc=",
"reactSRI": "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8=", "reactSRI": "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8=",
"reactDOMSRI": "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0=", "reactDOMSRI": "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0=",
}) })
@ -71,3 +81,27 @@ func Handler(title string, endpoint string) http.HandlerFunc {
} }
} }
} }
// endpointHasScheme checks if the endpoint has a scheme.
func endpointHasScheme(endpoint string) bool {
u, err := url.Parse(endpoint)
return err == nil && u.Scheme != ""
}
// getSubscriptionEndpoint returns the subscription endpoint for the given
// endpoint if it is parsable as a URL, or an empty string.
func getSubscriptionEndpoint(endpoint string) string {
u, err := url.Parse(endpoint)
if err != nil {
return ""
}
switch u.Scheme {
case "https":
u.Scheme = "wss"
default:
u.Scheme = "ws"
}
return u.String()
}

View file

@ -1,3 +1,3 @@
package graphql package graphql
const Version = "v0.17.1" const Version = "v0.17.9"

View file

@ -17,7 +17,8 @@ var mode = packages.NeedName |
packages.NeedTypes | packages.NeedTypes |
packages.NeedSyntax | packages.NeedSyntax |
packages.NeedTypesInfo | packages.NeedTypesInfo |
packages.NeedModule packages.NeedModule |
packages.NeedDeps
// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages // Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages
// that can be invalidated as writes are made and packages are known to change. // that can be invalidated as writes are made and packages are known to change.

View file

@ -16,12 +16,17 @@ import (
type federation struct { type federation struct {
Entities []*Entity Entities []*Entity
Version int
} }
// New returns a federation plugin that injects // New returns a federation plugin that injects
// federated directives and types into the schema // federated directives and types into the schema
func New() plugin.Plugin { func New(version int) plugin.Plugin {
return &federation{} if version == 0 {
version = 1
}
return &federation{Version: version}
} }
// Name returns the plugin name // Name returns the plugin name
@ -51,6 +56,7 @@ func (f *federation) MutateConfig(cfg *config.Config) error {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"},
}, },
} }
for typeName, entry := range builtins { for typeName, entry := range builtins {
if cfg.Models.Exists(typeName) { if cfg.Models.Exists(typeName) {
return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName) return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName)
@ -63,22 +69,46 @@ func (f *federation) MutateConfig(cfg *config.Config) error {
cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true}
// Federation 2 specific directives
if f.Version == 2 {
cfg.Directives["shareable"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["link"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["tag"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["override"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["inaccessible"] = config.DirectiveConfig{SkipRuntime: true}
}
return nil return nil
} }
func (f *federation) InjectSourceEarly() *ast.Source { func (f *federation) InjectSourceEarly() *ast.Source {
return &ast.Source{ input := `
Name: "federation/directives.graphql",
Input: `
scalar _Any scalar _Any
scalar _FieldSet scalar _FieldSet
directive @external on FIELD_DEFINITION directive @external on FIELD_DEFINITION
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
directive @extends on OBJECT | INTERFACE directive @extends on OBJECT | INTERFACE
`, `
// add version-specific changes on key directive, as well as adding the new directives for federation 2
if f.Version == 1 {
input += `
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
`
} else if f.Version == 2 {
input += `
directive @key(fields: _FieldSet!, resolvable: Boolean) repeatable on OBJECT | INTERFACE
directive @link(import: [String!], url: String!) repeatable on SCHEMA
directive @shareable on OBJECT | FIELD_DEFINITION
directive @tag repeatable on OBJECT | FIELD_DEFINITION | INTERFACE | UNION
directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
`
}
return &ast.Source{
Name: "federation/directives.graphql",
Input: input,
BuiltIn: true, BuiltIn: true,
} }
} }
@ -290,10 +320,21 @@ func (f *federation) setEntities(schema *ast.Schema) {
// } // }
if !e.allFieldsAreExternal() { if !e.allFieldsAreExternal() {
for _, dir := range keys { for _, dir := range keys {
if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { if len(dir.Arguments) > 2 {
panic("Exactly one `fields` argument needed for @key declaration.") panic("More than two arguments provided for @key declaration.")
} }
arg := dir.Arguments[0] var arg *ast.Argument
// since keys are able to now have multiple arguments, we need to check both possible for a possible @key(fields="" fields="")
for _, a := range dir.Arguments {
if a.Name == "fields" {
if arg != nil {
panic("More than one `fields` provided for @key declaration.")
}
arg = a
}
}
keyFieldSet := fieldset.New(arg.Value.Raw, nil) keyFieldSet := fieldset.New(arg.Value.Raw, nil)
keyFields := make([]*KeyField, len(keyFieldSet)) keyFields := make([]*KeyField, len(keyFieldSet))

View file

@ -1,5 +0,0 @@
*.sublime-*
.DS_Store
*.swp
*.swo
tags

View file

@ -1,12 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- "1.10.x"
- "1.11.x"
- tip

View file

@ -1,12 +0,0 @@
Copyright (c) 2012, Martin Angers
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 the author 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 HOLDER 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.

View file

@ -1,188 +0,0 @@
# Purell
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
[![build status](https://travis-ci.org/PuerkitoBio/purell.svg?branch=master)](http://travis-ci.org/PuerkitoBio/purell)
## Install
`go get github.com/PuerkitoBio/purell`
## Changelog
* **v1.1.1** : Fix failing test due to Go1.12 changes (thanks to @ianlancetaylor).
* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121).
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
* **v0.2.0** : Add benchmarks, Attempt IDN support.
* **v0.1.0** : Initial release.
## Examples
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
```go
package purell
import (
"fmt"
"net/url"
)
func ExampleNormalizeURLString() {
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
panic(err)
} else {
fmt.Print(normalized)
}
// Output: http://somewebsite.com:80/Amazing%3F/url/
}
func ExampleMustNormalizeURLString() {
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
FlagsUnsafeGreedy)
fmt.Print(normalized)
// Output: http://somewebsite.com/Amazing%FA/url
}
func ExampleNormalizeURL() {
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
panic(err)
} else {
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
fmt.Print(normalized)
}
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
}
```
## API
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
```go
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
```
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
The [full godoc reference is available on gopkgdoc][godoc].
Some things to note:
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
- %24 -> $
- %26 -> &
- %2B-%3B -> +,-./0123456789:;
- %3D -> =
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
- %5F -> _
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
- %7E -> ~
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
### Safe vs Usually Safe vs Unsafe
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
Consider the following URL:
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
Normalizing with the `FlagsSafe` gives:
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
With the `FlagsUsuallySafeGreedy`:
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
And with `FlagsUnsafeGreedy`:
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
## TODOs
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
## Thanks / Contributions
@rogpeppe
@jehiah
@opennota
@pchristopher1275
@zenovich
@beeker1121
## License
The [BSD 3-Clause license][bsd].
[bsd]: http://opensource.org/licenses/BSD-3-Clause
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
[iss7]: https://github.com/PuerkitoBio/purell/issues/7

View file

@ -1,379 +0,0 @@
/*
Package purell offers URL normalization as described on the wikipedia page:
http://en.wikipedia.org/wiki/URL_normalization
*/
package purell
import (
"bytes"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/PuerkitoBio/urlesc"
"golang.org/x/net/idna"
"golang.org/x/text/unicode/norm"
"golang.org/x/text/width"
)
// A set of normalization flags determines how a URL will
// be normalized.
type NormalizationFlags uint
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
const (
defaultHttpPort = ":80"
defaultHttpsPort = ":443"
)
// Regular expressions used by the normalizations
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
var rxEmptyPort = regexp.MustCompile(`:+$`)
// Map of flags to implementation function.
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
// Since maps have undefined traversing order, make a slice of ordered keys
var flagsOrder = []NormalizationFlags{
FlagLowercaseScheme,
FlagLowercaseHost,
FlagRemoveDefaultPort,
FlagRemoveDirectoryIndex,
FlagRemoveDotSegments,
FlagRemoveFragment,
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
FlagRemoveDuplicateSlashes,
FlagRemoveWWW,
FlagAddWWW,
FlagSortQuery,
FlagDecodeDWORDHost,
FlagDecodeOctalHost,
FlagDecodeHexHost,
FlagRemoveUnnecessaryHostDots,
FlagRemoveEmptyPortSeparator,
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
FlagAddTrailingSlash,
}
// ... and then the map, where order is unimportant
var flags = map[NormalizationFlags]func(*url.URL){
FlagLowercaseScheme: lowercaseScheme,
FlagLowercaseHost: lowercaseHost,
FlagRemoveDefaultPort: removeDefaultPort,
FlagRemoveDirectoryIndex: removeDirectoryIndex,
FlagRemoveDotSegments: removeDotSegments,
FlagRemoveFragment: removeFragment,
FlagForceHTTP: forceHTTP,
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
FlagRemoveWWW: removeWWW,
FlagAddWWW: addWWW,
FlagSortQuery: sortQuery,
FlagDecodeDWORDHost: decodeDWORDHost,
FlagDecodeOctalHost: decodeOctalHost,
FlagDecodeHexHost: decodeHexHost,
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
FlagRemoveTrailingSlash: removeTrailingSlash,
FlagAddTrailingSlash: addTrailingSlash,
}
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
// It takes an URL string as input, as well as the normalization flags.
func MustNormalizeURLString(u string, f NormalizationFlags) string {
result, e := NormalizeURLString(u, f)
if e != nil {
panic(e)
}
return result
}
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
// It takes an URL string as input, as well as the normalization flags.
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", err
}
if f&FlagLowercaseHost == FlagLowercaseHost {
parsed.Host = strings.ToLower(parsed.Host)
}
// The idna package doesn't fully conform to RFC 5895
// (https://tools.ietf.org/html/rfc5895), so we do it here.
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
// TODO: Remove when (if?) idna package conforms to RFC 5895.
parsed.Host = width.Fold.String(parsed.Host)
parsed.Host = norm.NFC.String(parsed.Host)
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
return "", err
}
return NormalizeURL(parsed, f), nil
}
// NormalizeURL returns the normalized string.
// It takes a parsed URL object as input, as well as the normalization flags.
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
for _, k := range flagsOrder {
if f&k == k {
flags[k](u)
}
}
return urlesc.Escape(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 removeTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if strings.HasSuffix(u.Path, "/") {
u.Path = u.Path[:l-1]
}
} else if l = len(u.Host); l > 0 {
if strings.HasSuffix(u.Host, "/") {
u.Host = u.Host[:l-1]
}
}
}
func addTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
} else if l = len(u.Host); l > 0 {
if !strings.HasSuffix(u.Host, "/") {
u.Host += "/"
}
}
}
func removeDotSegments(u *url.URL) {
if len(u.Path) > 0 {
var dotFree []string
var lastIsDot bool
sections := strings.Split(u.Path, "/")
for _, s := range sections {
if s == ".." {
if len(dotFree) > 0 {
dotFree = dotFree[:len(dotFree)-1]
}
} else if s != "." {
dotFree = append(dotFree, s)
}
lastIsDot = (s == "." || s == "..")
}
// Special case if host does not end with / and new path does not begin with /
u.Path = strings.Join(dotFree, "/")
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
u.Path = "/" + u.Path
}
// Special case if the last segment was a dot, make sure the path ends with a slash
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
}
}
func removeDirectoryIndex(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
}
}
func removeFragment(u *url.URL) {
u.Fragment = ""
}
func forceHTTP(u *url.URL) {
if strings.ToLower(u.Scheme) == "https" {
u.Scheme = "http"
}
}
func removeDuplicateSlashes(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
}
}
func removeWWW(u *url.URL) {
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = u.Host[4:]
}
}
func addWWW(u *url.URL) {
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = "www." + u.Host
}
}
func sortQuery(u *url.URL) {
q := u.Query()
if len(q) > 0 {
arKeys := make([]string, len(q))
i := 0
for k := range q {
arKeys[i] = k
i++
}
sort.Strings(arKeys)
buf := new(bytes.Buffer)
for _, k := range arKeys {
sort.Strings(q[k])
for _, v := range q[k] {
if buf.Len() > 0 {
buf.WriteRune('&')
}
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
}
}
// Rebuild the raw query string
u.RawQuery = buf.String()
}
}
func decodeDWORDHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
var parts [4]int64
dword, _ := strconv.ParseInt(matches[1], 10, 0)
for i, shift := range []uint{24, 16, 8, 0} {
parts[i] = dword >> shift & 0xFF
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
}
}
}
func decodeOctalHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
var parts [4]int64
for i := 1; i <= 4; i++ {
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
}
}
}
func decodeHexHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
// Conversion is safe because of regex validation
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
// Set host as DWORD (base 10) encoded host
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
// The rest is the same as decoding a DWORD host
decodeDWORDHost(u)
}
}
}
func removeUnncessaryHostDots(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
// Trim the leading and trailing dots
u.Host = strings.Trim(matches[1], ".")
if len(matches) > 2 {
u.Host += matches[2]
}
}
}
}
func removeEmptyPortSeparator(u *url.URL) {
if len(u.Host) > 0 {
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
}
}

View file

@ -1,15 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- tip
install:
- go build .
script:
- go test -v

View file

@ -1,27 +0,0 @@
Copyright (c) 2012 The Go 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.

View file

@ -1,16 +0,0 @@
urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.svg?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc)
======
Package urlesc implements query escaping as per RFC 3986.
It contains some parts of the net/url package, modified so as to allow
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
## Install
go get github.com/PuerkitoBio/urlesc
## License
Go license (BSD-3-Clause)

View file

@ -1,180 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package urlesc implements query escaping as per RFC 3986.
// It contains some parts of the net/url package, modified so as to allow
// some reserved characters incorrectly escaped by net/url.
// See https://github.com/golang/go/issues/5684
package urlesc
import (
"bytes"
"net/url"
"strings"
)
type encoding int
const (
encodePath encoding = 1 + iota
encodeUserPassword
encodeQueryComponent
encodeFragment
)
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
func shouldEscape(c byte, mode encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
return false
// §2.2 Reserved characters (reserved)
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case encodePath: // §3.3
// The RFC allows sub-delims and : @.
// '/', '[' and ']' can be used to assign meaning to individual path
// segments. This package only manipulates the path as a whole,
// so we allow those as well. That leaves only ? and # to escape.
return c == '?' || c == '#'
case encodeUserPassword: // §3.2.1
// The RFC allows : and sub-delims in
// userinfo. The parsing of userinfo treats ':' as special so we must escape
// all the gen-delims.
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
case encodeQueryComponent: // §3.4
// The RFC allows / and ?.
return c != '/' && c != '?'
case encodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing but #
return c == '#'
}
}
// Everything else must be escaped.
return true
}
// QueryEscape escapes the string so it can be safely placed
// inside a URL query.
func QueryEscape(s string) string {
return escape(s, encodeQueryComponent)
}
func escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == encodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == encodeQueryComponent:
t[j] = '+'
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}
var uiReplacer = strings.NewReplacer(
"%21", "!",
"%27", "'",
"%28", "(",
"%29", ")",
"%2A", "*",
)
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
func unescapeUserinfo(s string) string {
return uiReplacer.Replace(s)
}
// Escape reassembles the URL into a valid URL string.
// The general form of the result is one of:
//
// scheme:opaque
// scheme://userinfo@host/path?query#fragment
//
// If u.Opaque is non-empty, String uses the first form;
// otherwise it uses the second form.
//
// In the second form, the following rules apply:
// - if u.Scheme is empty, scheme: is omitted.
// - if u.User is nil, userinfo@ is omitted.
// - if u.Host is empty, host/ is omitted.
// - if u.Scheme and u.Host are empty and u.User is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.Host is non-empty and u.Path begins with a /,
// the form host/path does not add its own /.
// - if u.RawQuery is empty, ?query is omitted.
// - if u.Fragment is empty, #fragment is omitted.
func Escape(u *url.URL) string {
var buf bytes.Buffer
if u.Scheme != "" {
buf.WriteString(u.Scheme)
buf.WriteByte(':')
}
if u.Opaque != "" {
buf.WriteString(u.Opaque)
} else {
if u.Scheme != "" || u.Host != "" || u.User != nil {
buf.WriteString("//")
if ui := u.User; ui != nil {
buf.WriteString(unescapeUserinfo(ui.String()))
buf.WriteByte('@')
}
if h := u.Host; h != "" {
buf.WriteString(h)
}
}
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(escape(u.Path, encodePath))
}
if u.RawQuery != "" {
buf.WriteByte('?')
buf.WriteString(u.RawQuery)
}
if u.Fragment != "" {
buf.WriteByte('#')
buf.WriteString(escape(u.Fragment, encodeFragment))
}
return buf.String()
}

View file

@ -1,11 +1,8 @@
module github.com/go-openapi/jsonreference module github.com/go-openapi/jsonreference
require ( require (
github.com/PuerkitoBio/purell v1.1.1
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/go-openapi/jsonpointer v0.19.3 github.com/go-openapi/jsonpointer v0.19.3
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 // indirect
) )
go 1.13 go 1.13

View file

@ -1,7 +1,3 @@
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -22,14 +18,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View 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, "/")
}
}

View file

@ -30,8 +30,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/PuerkitoBio/purell"
"github.com/go-openapi/jsonpointer" "github.com/go-openapi/jsonpointer"
"github.com/go-openapi/jsonreference/internal"
) )
const ( const (
@ -114,7 +114,9 @@ func (r *Ref) parse(jsonReferenceString string) error {
return err return err
} }
r.referenceURL, _ = url.Parse(purell.NormalizeURL(parsed, purell.FlagsSafe|purell.FlagRemoveDuplicateSlashes)) internal.NormalizeURL(parsed)
r.referenceURL = parsed
refURL := r.referenceURL refURL := r.referenceURL
if refURL.Scheme != "" && refURL.Host != "" { if refURL.Scheme != "" && refURL.Host != "" {

View file

@ -2,10 +2,9 @@ module github.com/go-openapi/spec
require ( require (
github.com/go-openapi/jsonpointer v0.19.5 github.com/go-openapi/jsonpointer v0.19.5
github.com/go-openapi/jsonreference v0.19.6 github.com/go-openapi/jsonreference v0.20.0
github.com/go-openapi/swag v0.19.15 github.com/go-openapi/swag v0.19.15
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )

View file

@ -1,17 +1,12 @@
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 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 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 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 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
@ -19,12 +14,10 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/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 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -33,19 +26,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
@ -53,7 +36,6 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,7 +1,7 @@
Package validator Package validator
================= =================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-10.10.1-green.svg) ![Project status](https://img.shields.io/badge/version-10.11.0-green.svg)
[![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@ -130,7 +130,7 @@ Baked-in Validations
| contains | Contains | | contains | Contains |
| containsany | Contains Any | | containsany | Contains Any |
| containsrune | Contains Rune | | containsrune | Contains Rune |
| endsnotwith | Ends With | | endsnotwith | Ends Not With |
| endswith | Ends With | | endswith | Ends With |
| excludes | Excludes | | excludes | Excludes |
| excludesall | Excludes All | | excludesall | Excludes All |
@ -153,6 +153,7 @@ Baked-in Validations
| bcp47_language_tag | Language tag (BCP 47) | | bcp47_language_tag | Language tag (BCP 47) |
| btc_addr | Bitcoin Address | | btc_addr | Bitcoin Address |
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) | | btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
| credit_card | Credit Card Number |
| datetime | Datetime | | datetime | Datetime |
| e164 | e164 formatted phone number | | e164 | e164 formatted phone number |
| email | E-mail String | email | E-mail String
@ -189,6 +190,16 @@ Baked-in Validations
| uuid5 | Universally Unique Identifier UUID v5 | | uuid5 | Universally Unique Identifier UUID v5 |
| uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | | uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 |
| uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | | uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 |
| md4 | MD4 hash |
| md5 | MD5 hash |
| sha256 | SHA256 hash |
| sha384 | SHA384 hash |
| sha512 | SHA512 hash |
| ripemd128 | RIPEMD-128 hash |
| ripemd128 | RIPEMD-160 hash |
| tiger128 | TIGER128 hash |
| tiger160 | TIGER160 hash |
| tiger192 | TIGER192 hash |
| semver | Semantic Versioning 2.0.0 | | semver | Semantic Versioning 2.0.0 |
| ulid | Universally Unique Lexicographically Sortable Identifier ULID | | ulid | Universally Unique Lexicographically Sortable Identifier ULID |
@ -219,6 +230,8 @@ Baked-in Validations
| required_with_all | Required With All | | required_with_all | Required With All |
| required_without | Required Without | | required_without | Required Without |
| required_without_all | Required Without All | | required_without_all | Required Without All |
| excluded_if | Excluded If |
| excluded_unless | Excluded Unless |
| excluded_with | Excluded With | | excluded_with | Excluded With |
| excluded_with_all | Excluded With All | | excluded_with_all | Excluded With All |
| excluded_without | Excluded Without | | excluded_without | Excluded Without |

View file

@ -75,6 +75,8 @@ var (
"required_with_all": requiredWithAll, "required_with_all": requiredWithAll,
"required_without": requiredWithout, "required_without": requiredWithout,
"required_without_all": requiredWithoutAll, "required_without_all": requiredWithoutAll,
"excluded_if": excludedIf,
"excluded_unless": excludedUnless,
"excluded_with": excludedWith, "excluded_with": excludedWith,
"excluded_with_all": excludedWithAll, "excluded_with_all": excludedWithAll,
"excluded_without": excludedWithout, "excluded_without": excludedWithout,
@ -149,6 +151,16 @@ var (
"uuid4_rfc4122": isUUID4RFC4122, "uuid4_rfc4122": isUUID4RFC4122,
"uuid5_rfc4122": isUUID5RFC4122, "uuid5_rfc4122": isUUID5RFC4122,
"ulid": isULID, "ulid": isULID,
"md4": isMD4,
"md5": isMD5,
"sha256": isSHA256,
"sha384": isSHA384,
"sha512": isSHA512,
"ripemd128": isRIPEMD128,
"ripemd160": isRIPEMD160,
"tiger128": isTIGER128,
"tiger160": isTIGER160,
"tiger192": isTIGER192,
"ascii": isASCII, "ascii": isASCII,
"printascii": isPrintableASCII, "printascii": isPrintableASCII,
"multibyte": hasMultiByteCharacter, "multibyte": hasMultiByteCharacter,
@ -201,11 +213,14 @@ var (
"bic": isIsoBicFormat, "bic": isIsoBicFormat,
"semver": isSemverFormat, "semver": isSemverFormat,
"dns_rfc1035_label": isDnsRFC1035LabelFormat, "dns_rfc1035_label": isDnsRFC1035LabelFormat,
"credit_card": isCreditCard,
} }
) )
var oneofValsCache = map[string][]string{} var (
var oneofValsCacheRWLock = sync.RWMutex{} oneofValsCache = map[string][]string{}
oneofValsCacheRWLock = sync.RWMutex{}
)
func parseOneOfParam2(s string) []string { func parseOneOfParam2(s string) []string {
oneofValsCacheRWLock.RLock() oneofValsCacheRWLock.RLock()
@ -261,7 +276,6 @@ func isOneOf(fl FieldLevel) bool {
// isUnique is the validation function for validating if each array|slice|map value is unique // isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool { func isUnique(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
v := reflect.ValueOf(struct{}{}) v := reflect.ValueOf(struct{}{})
@ -311,7 +325,6 @@ func isUnique(fl FieldLevel) bool {
// isMAC is the validation function for validating if the field's value is a valid MAC address. // isMAC is the validation function for validating if the field's value is a valid MAC address.
func isMAC(fl FieldLevel) bool { func isMAC(fl FieldLevel) bool {
_, err := net.ParseMAC(fl.Field().String()) _, err := net.ParseMAC(fl.Field().String())
return err == nil return err == nil
@ -319,7 +332,6 @@ func isMAC(fl FieldLevel) bool {
// isCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address. // isCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address.
func isCIDRv4(fl FieldLevel) bool { func isCIDRv4(fl FieldLevel) bool {
ip, _, err := net.ParseCIDR(fl.Field().String()) ip, _, err := net.ParseCIDR(fl.Field().String())
return err == nil && ip.To4() != nil return err == nil && ip.To4() != nil
@ -327,7 +339,6 @@ func isCIDRv4(fl FieldLevel) bool {
// isCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address. // isCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address.
func isCIDRv6(fl FieldLevel) bool { func isCIDRv6(fl FieldLevel) bool {
ip, _, err := net.ParseCIDR(fl.Field().String()) ip, _, err := net.ParseCIDR(fl.Field().String())
return err == nil && ip.To4() == nil return err == nil && ip.To4() == nil
@ -335,7 +346,6 @@ func isCIDRv6(fl FieldLevel) bool {
// isCIDR is the validation function for validating if the field's value is a valid v4 or v6 CIDR address. // isCIDR is the validation function for validating if the field's value is a valid v4 or v6 CIDR address.
func isCIDR(fl FieldLevel) bool { func isCIDR(fl FieldLevel) bool {
_, _, err := net.ParseCIDR(fl.Field().String()) _, _, err := net.ParseCIDR(fl.Field().String())
return err == nil return err == nil
@ -343,7 +353,6 @@ func isCIDR(fl FieldLevel) bool {
// isIPv4 is the validation function for validating if a value is a valid v4 IP address. // isIPv4 is the validation function for validating if a value is a valid v4 IP address.
func isIPv4(fl FieldLevel) bool { func isIPv4(fl FieldLevel) bool {
ip := net.ParseIP(fl.Field().String()) ip := net.ParseIP(fl.Field().String())
return ip != nil && ip.To4() != nil return ip != nil && ip.To4() != nil
@ -351,7 +360,6 @@ func isIPv4(fl FieldLevel) bool {
// isIPv6 is the validation function for validating if the field's value is a valid v6 IP address. // isIPv6 is the validation function for validating if the field's value is a valid v6 IP address.
func isIPv6(fl FieldLevel) bool { func isIPv6(fl FieldLevel) bool {
ip := net.ParseIP(fl.Field().String()) ip := net.ParseIP(fl.Field().String())
return ip != nil && ip.To4() == nil return ip != nil && ip.To4() == nil
@ -359,7 +367,6 @@ func isIPv6(fl FieldLevel) bool {
// isIP is the validation function for validating if the field's value is a valid v4 or v6 IP address. // isIP is the validation function for validating if the field's value is a valid v4 or v6 IP address.
func isIP(fl FieldLevel) bool { func isIP(fl FieldLevel) bool {
ip := net.ParseIP(fl.Field().String()) ip := net.ParseIP(fl.Field().String())
return ip != nil return ip != nil
@ -367,7 +374,6 @@ func isIP(fl FieldLevel) bool {
// isSSN is the validation function for validating if the field's value is a valid SSN. // isSSN is the validation function for validating if the field's value is a valid SSN.
func isSSN(fl FieldLevel) bool { func isSSN(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
if field.Len() != 11 { if field.Len() != 11 {
@ -425,7 +431,6 @@ func isLatitude(fl FieldLevel) bool {
// isDataURI is the validation function for validating if the field's value is a valid data URI. // isDataURI is the validation function for validating if the field's value is a valid data URI.
func isDataURI(fl FieldLevel) bool { func isDataURI(fl FieldLevel) bool {
uri := strings.SplitN(fl.Field().String(), ",", 2) uri := strings.SplitN(fl.Field().String(), ",", 2)
if len(uri) != 2 { if len(uri) != 2 {
@ -441,7 +446,6 @@ func isDataURI(fl FieldLevel) bool {
// hasMultiByteCharacter is the validation function for validating if the field's value has a multi byte character. // hasMultiByteCharacter is the validation function for validating if the field's value has a multi byte character.
func hasMultiByteCharacter(fl FieldLevel) bool { func hasMultiByteCharacter(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
if field.Len() == 0 { if field.Len() == 0 {
@ -506,6 +510,56 @@ func isULID(fl FieldLevel) bool {
return uLIDRegex.MatchString(fl.Field().String()) return uLIDRegex.MatchString(fl.Field().String())
} }
// isMD4 is the validation function for validating if the field's value is a valid MD4.
func isMD4(fl FieldLevel) bool {
return md4Regex.MatchString(fl.Field().String())
}
// isMD5 is the validation function for validating if the field's value is a valid MD5.
func isMD5(fl FieldLevel) bool {
return md5Regex.MatchString(fl.Field().String())
}
// isSHA256 is the validation function for validating if the field's value is a valid SHA256.
func isSHA256(fl FieldLevel) bool {
return sha256Regex.MatchString(fl.Field().String())
}
// isSHA384 is the validation function for validating if the field's value is a valid SHA384.
func isSHA384(fl FieldLevel) bool {
return sha384Regex.MatchString(fl.Field().String())
}
// isSHA512 is the validation function for validating if the field's value is a valid SHA512.
func isSHA512(fl FieldLevel) bool {
return sha512Regex.MatchString(fl.Field().String())
}
// isRIPEMD128 is the validation function for validating if the field's value is a valid PIPEMD128.
func isRIPEMD128(fl FieldLevel) bool {
return ripemd128Regex.MatchString(fl.Field().String())
}
// isRIPEMD160 is the validation function for validating if the field's value is a valid PIPEMD160.
func isRIPEMD160(fl FieldLevel) bool {
return ripemd160Regex.MatchString(fl.Field().String())
}
// isTIGER128 is the validation function for validating if the field's value is a valid TIGER128.
func isTIGER128(fl FieldLevel) bool {
return tiger128Regex.MatchString(fl.Field().String())
}
// isTIGER160 is the validation function for validating if the field's value is a valid TIGER160.
func isTIGER160(fl FieldLevel) bool {
return tiger160Regex.MatchString(fl.Field().String())
}
// isTIGER192 is the validation function for validating if the field's value is a valid isTIGER192.
func isTIGER192(fl FieldLevel) bool {
return tiger192Regex.MatchString(fl.Field().String())
}
// isISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN. // isISBN is the validation function for validating if the field's value is a valid v10 or v13 ISBN.
func isISBN(fl FieldLevel) bool { func isISBN(fl FieldLevel) bool {
return isISBN10(fl) || isISBN13(fl) return isISBN10(fl) || isISBN13(fl)
@ -513,7 +567,6 @@ func isISBN(fl FieldLevel) bool {
// isISBN13 is the validation function for validating if the field's value is a valid v13 ISBN. // isISBN13 is the validation function for validating if the field's value is a valid v13 ISBN.
func isISBN13(fl FieldLevel) bool { func isISBN13(fl FieldLevel) bool {
s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 4), " ", "", 4) s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 4), " ", "", 4)
if !iSBN13Regex.MatchString(s) { if !iSBN13Regex.MatchString(s) {
@ -534,7 +587,6 @@ func isISBN13(fl FieldLevel) bool {
// isISBN10 is the validation function for validating if the field's value is a valid v10 ISBN. // isISBN10 is the validation function for validating if the field's value is a valid v10 ISBN.
func isISBN10(fl FieldLevel) bool { func isISBN10(fl FieldLevel) bool {
s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 3), " ", "", 3) s := strings.Replace(strings.Replace(fl.Field().String(), "-", "", 3), " ", "", 3)
if !iSBN10Regex.MatchString(s) { if !iSBN10Regex.MatchString(s) {
@ -722,7 +774,6 @@ func excludes(fl FieldLevel) bool {
// containsRune is the validation function for validating that the field's value contains the rune specified within the param. // containsRune is the validation function for validating that the field's value contains the rune specified within the param.
func containsRune(fl FieldLevel) bool { func containsRune(fl FieldLevel) bool {
r, _ := utf8.DecodeRuneInString(fl.Param()) r, _ := utf8.DecodeRuneInString(fl.Param())
return strings.ContainsRune(fl.Field().String(), r) return strings.ContainsRune(fl.Field().String(), r)
@ -785,7 +836,6 @@ func fieldExcludes(fl FieldLevel) bool {
// isNeField is the validation function for validating if the current field's value is not equal to the field specified by the param's value. // isNeField is the validation function for validating if the current field's value is not equal to the field specified by the param's value.
func isNeField(fl FieldLevel) bool { func isNeField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -816,12 +866,7 @@ func isNeField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
// Not Same underlying type i.e. struct and time if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
if fieldType != currentField.Type() {
return true
}
if fieldType == timeType {
t := currentField.Interface().(time.Time) t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time) fieldTime := field.Interface().(time.Time)
@ -829,6 +874,10 @@ func isNeField(fl FieldLevel) bool {
return !fieldTime.Equal(t) return !fieldTime.Equal(t)
} }
// Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() {
return true
}
} }
// default reflect.String: // default reflect.String:
@ -842,7 +891,6 @@ func isNe(fl FieldLevel) bool {
// isLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value. // isLteCrossStructField is the validation function for validating if the current field's value is less than or equal to the field, within a separate struct, specified by the param's value.
func isLteCrossStructField(fl FieldLevel) bool { func isLteCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -869,18 +917,18 @@ func isLteCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := field.Convert(timeType).Interface().(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(topTime) || fieldTime.Equal(topTime)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return false return false
} }
if fieldType == timeType {
fieldTime := field.Interface().(time.Time)
topTime := topField.Interface().(time.Time)
return fieldTime.Before(topTime) || fieldTime.Equal(topTime)
}
} }
// default reflect.String: // default reflect.String:
@ -890,7 +938,6 @@ func isLteCrossStructField(fl FieldLevel) bool {
// isLtCrossStructField is the validation function for validating if the current field's value is less than the field, within a separate struct, specified by the param's value. // isLtCrossStructField is the validation function for validating if the current field's value is less than the field, within a separate struct, specified by the param's value.
// NOTE: This is exposed for use within your own custom functions and not intended to be called directly. // NOTE: This is exposed for use within your own custom functions and not intended to be called directly.
func isLtCrossStructField(fl FieldLevel) bool { func isLtCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -917,18 +964,18 @@ func isLtCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := field.Convert(timeType).Interface().(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(topTime)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return false return false
} }
if fieldType == timeType {
fieldTime := field.Interface().(time.Time)
topTime := topField.Interface().(time.Time)
return fieldTime.Before(topTime)
}
} }
// default reflect.String: // default reflect.String:
@ -937,7 +984,6 @@ func isLtCrossStructField(fl FieldLevel) bool {
// isGteCrossStructField is the validation function for validating if the current field's value is greater than or equal to the field, within a separate struct, specified by the param's value. // isGteCrossStructField is the validation function for validating if the current field's value is greater than or equal to the field, within a separate struct, specified by the param's value.
func isGteCrossStructField(fl FieldLevel) bool { func isGteCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -964,18 +1010,18 @@ func isGteCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := field.Convert(timeType).Interface().(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.After(topTime) || fieldTime.Equal(topTime)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return false return false
} }
if fieldType == timeType {
fieldTime := field.Interface().(time.Time)
topTime := topField.Interface().(time.Time)
return fieldTime.After(topTime) || fieldTime.Equal(topTime)
}
} }
// default reflect.String: // default reflect.String:
@ -984,7 +1030,6 @@ func isGteCrossStructField(fl FieldLevel) bool {
// isGtCrossStructField is the validation function for validating if the current field's value is greater than the field, within a separate struct, specified by the param's value. // isGtCrossStructField is the validation function for validating if the current field's value is greater than the field, within a separate struct, specified by the param's value.
func isGtCrossStructField(fl FieldLevel) bool { func isGtCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1011,18 +1056,18 @@ func isGtCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := field.Convert(timeType).Interface().(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.After(topTime)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return false return false
} }
if fieldType == timeType {
fieldTime := field.Interface().(time.Time)
topTime := topField.Interface().(time.Time)
return fieldTime.After(topTime)
}
} }
// default reflect.String: // default reflect.String:
@ -1031,7 +1076,6 @@ func isGtCrossStructField(fl FieldLevel) bool {
// isNeCrossStructField is the validation function for validating that the current field's value is not equal to the field, within a separate struct, specified by the param's value. // isNeCrossStructField is the validation function for validating that the current field's value is not equal to the field, within a separate struct, specified by the param's value.
func isNeCrossStructField(fl FieldLevel) bool { func isNeCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1061,18 +1105,18 @@ func isNeCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
t := field.Convert(timeType).Interface().(time.Time)
fieldTime := topField.Convert(timeType).Interface().(time.Time)
return !fieldTime.Equal(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return true return true
} }
if fieldType == timeType {
t := field.Interface().(time.Time)
fieldTime := topField.Interface().(time.Time)
return !fieldTime.Equal(t)
}
} }
// default reflect.String: // default reflect.String:
@ -1081,7 +1125,6 @@ func isNeCrossStructField(fl FieldLevel) bool {
// isEqCrossStructField is the validation function for validating that the current field's value is equal to the field, within a separate struct, specified by the param's value. // isEqCrossStructField is the validation function for validating that the current field's value is equal to the field, within a separate struct, specified by the param's value.
func isEqCrossStructField(fl FieldLevel) bool { func isEqCrossStructField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1111,18 +1154,18 @@ func isEqCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
t := field.Convert(timeType).Interface().(time.Time)
fieldTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Equal(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != topField.Type() { if fieldType != topField.Type() {
return false return false
} }
if fieldType == timeType {
t := field.Interface().(time.Time)
fieldTime := topField.Interface().(time.Time)
return fieldTime.Equal(t)
}
} }
// default reflect.String: // default reflect.String:
@ -1131,7 +1174,6 @@ func isEqCrossStructField(fl FieldLevel) bool {
// isEqField is the validation function for validating if the current field's value is equal to the field specified by the param's value. // isEqField is the validation function for validating if the current field's value is equal to the field specified by the param's value.
func isEqField(fl FieldLevel) bool { func isEqField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1161,19 +1203,18 @@ func isEqField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
// Not Same underlying type i.e. struct and time if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
if fieldType != currentField.Type() {
return false
}
if fieldType == timeType { t := currentField.Convert(timeType).Interface().(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time)
return fieldTime.Equal(t) return fieldTime.Equal(t)
} }
// Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() {
return false
}
} }
// default reflect.String: // default reflect.String:
@ -1182,7 +1223,6 @@ func isEqField(fl FieldLevel) bool {
// isEq is the validation function for validating if the current field's value is equal to the param's value. // isEq is the validation function for validating if the current field's value is equal to the param's value.
func isEq(fl FieldLevel) bool { func isEq(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -1234,7 +1274,7 @@ func isPostcodeByIso3166Alpha2(fl FieldLevel) bool {
return reg.MatchString(field.String()) return reg.MatchString(field.String())
} }
// isPostcodeByIso3166Alpha2 validates by field which represents for a value of country code in iso 3166 alpha 2 // isPostcodeByIso3166Alpha2Field validates by field which represents for a value of country code in iso 3166 alpha 2
// example: `postcode_iso3166_alpha2_field=CountryCode` // example: `postcode_iso3166_alpha2_field=CountryCode`
func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool { func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@ -1273,11 +1313,9 @@ func isBase64URL(fl FieldLevel) bool {
// isURI is the validation function for validating if the current field's value is a valid URI. // isURI is the validation function for validating if the current field's value is a valid URI.
func isURI(fl FieldLevel) bool { func isURI(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
s := field.String() s := field.String()
@ -1302,11 +1340,9 @@ func isURI(fl FieldLevel) bool {
// isURL is the validation function for validating if the current field's value is a valid URL. // isURL is the validation function for validating if the current field's value is a valid URL.
func isURL(fl FieldLevel) bool { func isURL(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
var i int var i int
@ -1339,7 +1375,6 @@ func isUrnRFC2141(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
str := field.String() str := field.String()
@ -1542,6 +1577,22 @@ func requiredIf(fl FieldLevel) bool {
return hasValue(fl) return hasValue(fl)
} }
// excludedIf is the validation function
// The field under validation must not be present or is empty only if all the other specified fields are equal to the value following with the specified field.
func excludedIf(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for excluded_if %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return false
}
}
return true
}
// requiredUnless is the validation function // requiredUnless is the validation function
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field. // The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
func requiredUnless(fl FieldLevel) bool { func requiredUnless(fl FieldLevel) bool {
@ -1558,6 +1609,21 @@ func requiredUnless(fl FieldLevel) bool {
return hasValue(fl) return hasValue(fl)
} }
// excludedUnless is the validation function
// The field under validation must not be present or is empty unless all the other specified fields are equal to the value following with the specified field.
func excludedUnless(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for excluded_unless %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true
}
}
return !hasValue(fl)
}
// excludedWith is the validation function // excludedWith is the validation function
// The field under validation must not be present or is empty if any of the other specified fields are present. // The field under validation must not be present or is empty if any of the other specified fields are present.
func excludedWith(fl FieldLevel) bool { func excludedWith(fl FieldLevel) bool {
@ -1650,7 +1716,6 @@ func requiredWithoutAll(fl FieldLevel) bool {
// isGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. // isGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value.
func isGteField(fl FieldLevel) bool { func isGteField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1677,18 +1742,18 @@ func isGteField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := currentField.Convert(timeType).Interface().(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.After(t) || fieldTime.Equal(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() { if fieldType != currentField.Type() {
return false return false
} }
if fieldType == timeType {
t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time)
return fieldTime.After(t) || fieldTime.Equal(t)
}
} }
// default reflect.String // default reflect.String
@ -1697,7 +1762,6 @@ func isGteField(fl FieldLevel) bool {
// isGtField is the validation function for validating if the current field's value is greater than the field specified by the param's value. // isGtField is the validation function for validating if the current field's value is greater than the field specified by the param's value.
func isGtField(fl FieldLevel) bool { func isGtField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1724,18 +1788,18 @@ func isGtField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := currentField.Convert(timeType).Interface().(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.After(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() { if fieldType != currentField.Type() {
return false return false
} }
if fieldType == timeType {
t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time)
return fieldTime.After(t)
}
} }
// default reflect.String // default reflect.String
@ -1744,7 +1808,6 @@ func isGtField(fl FieldLevel) bool {
// isGte is the validation function for validating if the current field's value is greater than or equal to the param's value. // isGte is the validation function for validating if the current field's value is greater than or equal to the param's value.
func isGte(fl FieldLevel) bool { func isGte(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -1777,10 +1840,10 @@ func isGte(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType { if field.Type().ConvertibleTo(timeType) {
now := time.Now().UTC() now := time.Now().UTC()
t := field.Interface().(time.Time) t := field.Convert(timeType).Interface().(time.Time)
return t.After(now) || t.Equal(now) return t.After(now) || t.Equal(now)
} }
@ -1791,7 +1854,6 @@ func isGte(fl FieldLevel) bool {
// isGt is the validation function for validating if the current field's value is greater than the param's value. // isGt is the validation function for validating if the current field's value is greater than the param's value.
func isGt(fl FieldLevel) bool { func isGt(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -1823,9 +1885,9 @@ func isGt(fl FieldLevel) bool {
return field.Float() > p return field.Float() > p
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType { if field.Type().ConvertibleTo(timeType) {
return field.Interface().(time.Time).After(time.Now().UTC()) return field.Convert(timeType).Interface().(time.Time).After(time.Now().UTC())
} }
} }
@ -1834,7 +1896,6 @@ func isGt(fl FieldLevel) bool {
// hasLengthOf is the validation function for validating if the current field's value is equal to the param's value. // hasLengthOf is the validation function for validating if the current field's value is equal to the param's value.
func hasLengthOf(fl FieldLevel) bool { func hasLengthOf(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -1876,7 +1937,6 @@ func hasMinOf(fl FieldLevel) bool {
// isLteField is the validation function for validating if the current field's value is less than or equal to the field specified by the param's value. // isLteField is the validation function for validating if the current field's value is less than or equal to the field specified by the param's value.
func isLteField(fl FieldLevel) bool { func isLteField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1903,18 +1963,18 @@ func isLteField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := currentField.Convert(timeType).Interface().(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(t) || fieldTime.Equal(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() { if fieldType != currentField.Type() {
return false return false
} }
if fieldType == timeType {
t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time)
return fieldTime.Before(t) || fieldTime.Equal(t)
}
} }
// default reflect.String // default reflect.String
@ -1923,7 +1983,6 @@ func isLteField(fl FieldLevel) bool {
// isLtField is the validation function for validating if the current field's value is less than the field specified by the param's value. // isLtField is the validation function for validating if the current field's value is less than the field specified by the param's value.
func isLtField(fl FieldLevel) bool { func isLtField(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
kind := field.Kind() kind := field.Kind()
@ -1950,18 +2009,18 @@ func isLtField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := currentField.Convert(timeType).Interface().(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(t)
}
// Not Same underlying type i.e. struct and time // Not Same underlying type i.e. struct and time
if fieldType != currentField.Type() { if fieldType != currentField.Type() {
return false return false
} }
if fieldType == timeType {
t := currentField.Interface().(time.Time)
fieldTime := field.Interface().(time.Time)
return fieldTime.Before(t)
}
} }
// default reflect.String // default reflect.String
@ -1970,7 +2029,6 @@ func isLtField(fl FieldLevel) bool {
// isLte is the validation function for validating if the current field's value is less than or equal to the param's value. // isLte is the validation function for validating if the current field's value is less than or equal to the param's value.
func isLte(fl FieldLevel) bool { func isLte(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -2003,10 +2061,10 @@ func isLte(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType { if field.Type().ConvertibleTo(timeType) {
now := time.Now().UTC() now := time.Now().UTC()
t := field.Interface().(time.Time) t := field.Convert(timeType).Interface().(time.Time)
return t.Before(now) || t.Equal(now) return t.Before(now) || t.Equal(now)
} }
@ -2017,7 +2075,6 @@ func isLte(fl FieldLevel) bool {
// isLt is the validation function for validating if the current field's value is less than the param's value. // isLt is the validation function for validating if the current field's value is less than the param's value.
func isLt(fl FieldLevel) bool { func isLt(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
param := fl.Param() param := fl.Param()
@ -2050,9 +2107,9 @@ func isLt(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type() == timeType { if field.Type().ConvertibleTo(timeType) {
return field.Interface().(time.Time).Before(time.Now().UTC()) return field.Convert(timeType).Interface().(time.Time).Before(time.Now().UTC())
} }
} }
@ -2066,7 +2123,6 @@ func hasMaxOf(fl FieldLevel) bool {
// isTCP4AddrResolvable is the validation function for validating if the field's value is a resolvable tcp4 address. // isTCP4AddrResolvable is the validation function for validating if the field's value is a resolvable tcp4 address.
func isTCP4AddrResolvable(fl FieldLevel) bool { func isTCP4AddrResolvable(fl FieldLevel) bool {
if !isIP4Addr(fl) { if !isIP4Addr(fl) {
return false return false
} }
@ -2077,7 +2133,6 @@ func isTCP4AddrResolvable(fl FieldLevel) bool {
// isTCP6AddrResolvable is the validation function for validating if the field's value is a resolvable tcp6 address. // isTCP6AddrResolvable is the validation function for validating if the field's value is a resolvable tcp6 address.
func isTCP6AddrResolvable(fl FieldLevel) bool { func isTCP6AddrResolvable(fl FieldLevel) bool {
if !isIP6Addr(fl) { if !isIP6Addr(fl) {
return false return false
} }
@ -2089,7 +2144,6 @@ func isTCP6AddrResolvable(fl FieldLevel) bool {
// isTCPAddrResolvable is the validation function for validating if the field's value is a resolvable tcp address. // isTCPAddrResolvable is the validation function for validating if the field's value is a resolvable tcp address.
func isTCPAddrResolvable(fl FieldLevel) bool { func isTCPAddrResolvable(fl FieldLevel) bool {
if !isIP4Addr(fl) && !isIP6Addr(fl) { if !isIP4Addr(fl) && !isIP6Addr(fl) {
return false return false
} }
@ -2101,7 +2155,6 @@ func isTCPAddrResolvable(fl FieldLevel) bool {
// isUDP4AddrResolvable is the validation function for validating if the field's value is a resolvable udp4 address. // isUDP4AddrResolvable is the validation function for validating if the field's value is a resolvable udp4 address.
func isUDP4AddrResolvable(fl FieldLevel) bool { func isUDP4AddrResolvable(fl FieldLevel) bool {
if !isIP4Addr(fl) { if !isIP4Addr(fl) {
return false return false
} }
@ -2113,7 +2166,6 @@ func isUDP4AddrResolvable(fl FieldLevel) bool {
// isUDP6AddrResolvable is the validation function for validating if the field's value is a resolvable udp6 address. // isUDP6AddrResolvable is the validation function for validating if the field's value is a resolvable udp6 address.
func isUDP6AddrResolvable(fl FieldLevel) bool { func isUDP6AddrResolvable(fl FieldLevel) bool {
if !isIP6Addr(fl) { if !isIP6Addr(fl) {
return false return false
} }
@ -2125,7 +2177,6 @@ func isUDP6AddrResolvable(fl FieldLevel) bool {
// isUDPAddrResolvable is the validation function for validating if the field's value is a resolvable udp address. // isUDPAddrResolvable is the validation function for validating if the field's value is a resolvable udp address.
func isUDPAddrResolvable(fl FieldLevel) bool { func isUDPAddrResolvable(fl FieldLevel) bool {
if !isIP4Addr(fl) && !isIP6Addr(fl) { if !isIP4Addr(fl) && !isIP6Addr(fl) {
return false return false
} }
@ -2137,7 +2188,6 @@ func isUDPAddrResolvable(fl FieldLevel) bool {
// isIP4AddrResolvable is the validation function for validating if the field's value is a resolvable ip4 address. // isIP4AddrResolvable is the validation function for validating if the field's value is a resolvable ip4 address.
func isIP4AddrResolvable(fl FieldLevel) bool { func isIP4AddrResolvable(fl FieldLevel) bool {
if !isIPv4(fl) { if !isIPv4(fl) {
return false return false
} }
@ -2149,7 +2199,6 @@ func isIP4AddrResolvable(fl FieldLevel) bool {
// isIP6AddrResolvable is the validation function for validating if the field's value is a resolvable ip6 address. // isIP6AddrResolvable is the validation function for validating if the field's value is a resolvable ip6 address.
func isIP6AddrResolvable(fl FieldLevel) bool { func isIP6AddrResolvable(fl FieldLevel) bool {
if !isIPv6(fl) { if !isIPv6(fl) {
return false return false
} }
@ -2161,7 +2210,6 @@ func isIP6AddrResolvable(fl FieldLevel) bool {
// isIPAddrResolvable is the validation function for validating if the field's value is a resolvable ip address. // isIPAddrResolvable is the validation function for validating if the field's value is a resolvable ip address.
func isIPAddrResolvable(fl FieldLevel) bool { func isIPAddrResolvable(fl FieldLevel) bool {
if !isIP(fl) { if !isIP(fl) {
return false return false
} }
@ -2173,14 +2221,12 @@ func isIPAddrResolvable(fl FieldLevel) bool {
// isUnixAddrResolvable is the validation function for validating if the field's value is a resolvable unix address. // isUnixAddrResolvable is the validation function for validating if the field's value is a resolvable unix address.
func isUnixAddrResolvable(fl FieldLevel) bool { func isUnixAddrResolvable(fl FieldLevel) bool {
_, err := net.ResolveUnixAddr("unix", fl.Field().String()) _, err := net.ResolveUnixAddr("unix", fl.Field().String())
return err == nil return err == nil
} }
func isIP4Addr(fl FieldLevel) bool { func isIP4Addr(fl FieldLevel) bool {
val := fl.Field().String() val := fl.Field().String()
if idx := strings.LastIndex(val, ":"); idx != -1 { if idx := strings.LastIndex(val, ":"); idx != -1 {
@ -2193,7 +2239,6 @@ func isIP4Addr(fl FieldLevel) bool {
} }
func isIP6Addr(fl FieldLevel) bool { func isIP6Addr(fl FieldLevel) bool {
val := fl.Field().String() val := fl.Field().String()
if idx := strings.LastIndex(val, ":"); idx != -1 { if idx := strings.LastIndex(val, ":"); idx != -1 {
@ -2436,3 +2481,41 @@ func isDnsRFC1035LabelFormat(fl FieldLevel) bool {
val := fl.Field().String() val := fl.Field().String()
return dnsRegexRFC1035Label.MatchString(val) return dnsRegexRFC1035Label.MatchString(val)
} }
// isCreditCard is the validation function for validating if the current field's value is a valid credit card number
func isCreditCard(fl FieldLevel) bool {
val := fl.Field().String()
var creditCard bytes.Buffer
segments := strings.Split(val, " ")
for _, segment := range segments {
if len(segment) < 3 {
return false
}
creditCard.WriteString(segment)
}
ccDigits := strings.Split(creditCard.String(), "")
size := len(ccDigits)
if size < 12 || size > 19 {
return false
}
sum := 0
for i, digit := range ccDigits {
value, err := strconv.Atoi(digit)
if err != nil {
return false
}
if size%2 == 0 && i%2 == 0 || size%2 == 1 && i%2 == 1 {
v := value * 2
if v >= 10 {
sum += 1 + (v % 10)
} else {
sum += v
}
} else {
sum += value
}
}
return (sum % 10) == 0
}

View file

@ -114,6 +114,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]} cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
numFields := current.NumField() numFields := current.NumField()
rules := v.rules[typ]
var ctag *cTag var ctag *cTag
var fld reflect.StructField var fld reflect.StructField
@ -128,7 +129,11 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
continue continue
} }
if rtag, ok := rules[fld.Name]; ok {
tag = rtag
} else {
tag = fld.Tag.Get(v.tagName) tag = fld.Tag.Get(v.tagName)
}
if tag == skipValidationTag { if tag == skipValidationTag {
continue continue

View file

@ -349,6 +349,40 @@ Example:
// require the field if the Field1 and Field2 is not present: // require the field if the Field1 and Field2 is not present:
Usage: required_without_all=Field1 Field2 Usage: required_without_all=Field1 Field2
Excluded If
The field under validation must not be present or not empty only if all
the other specified fields are equal to the value following the specified
field. For strings ensures value is not "". For slices, maps, pointers,
interfaces, channels and functions ensures the value is not nil.
Usage: excluded_if
Examples:
// exclude the field if the Field1 is equal to the parameter given:
Usage: excluded_if=Field1 foobar
// exclude the field if the Field1 and Field2 is equal to the value respectively:
Usage: excluded_if=Field1 foo Field2 bar
Excluded Unless
The field under validation must not be present or empty unless all
the other specified fields are equal to the value following the specified
field. For strings ensures value is not "". For slices, maps, pointers,
interfaces, channels and functions ensures the value is not nil.
Usage: excluded_unless
Examples:
// exclude the field unless the Field1 is equal to the parameter given:
Usage: excluded_unless=Field1 foobar
// exclude the field unless the Field1 and Field2 is equal to the value respectively:
Usage: excluded_unless=Field1 foo Field2 bar
Is Default Is Default
This validates that the value is the default value and is almost the This validates that the value is the default value and is almost the
@ -1283,6 +1317,12 @@ More information on https://semver.org/
Usage: semver Usage: semver
Credit Card
This validates that a string value contains a valid credit card number using Luhn algoritm.
Usage: credit_card
Alias Validators and Tags Alias Validators and Tags
NOTE: When returning an error, the tag returned in "FieldError" will be NOTE: When returning an error, the tag returned in "FieldError" will be

View file

@ -30,6 +30,16 @@ const (
uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$" uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$"
md4RegexString = "^[0-9a-f]{32}$"
md5RegexString = "^[0-9a-f]{32}$"
sha256RegexString = "^[0-9a-f]{64}$"
sha384RegexString = "^[0-9a-f]{96}$"
sha512RegexString = "^[0-9a-f]{128}$"
ripemd128RegexString = "^[0-9a-f]{32}$"
ripemd160RegexString = "^[0-9a-f]{40}$"
tiger128RegexString = "^[0-9a-f]{32}$"
tiger160RegexString = "^[0-9a-f]{40}$"
tiger192RegexString = "^[0-9a-f]{48}$"
aSCIIRegexString = "^[\x00-\x7F]*$" aSCIIRegexString = "^[\x00-\x7F]*$"
printableASCIIRegexString = "^[\x20-\x7E]*$" printableASCIIRegexString = "^[\x20-\x7E]*$"
multibyteRegexString = "[^\x00-\x7F]" multibyteRegexString = "[^\x00-\x7F]"
@ -38,8 +48,8 @@ const (
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$` sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$`
hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952 hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62})(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.') fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.')
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32 btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
@ -84,6 +94,16 @@ var (
uUID5RFC4122Regex = regexp.MustCompile(uUID5RFC4122RegexString) uUID5RFC4122Regex = regexp.MustCompile(uUID5RFC4122RegexString)
uUIDRFC4122Regex = regexp.MustCompile(uUIDRFC4122RegexString) uUIDRFC4122Regex = regexp.MustCompile(uUIDRFC4122RegexString)
uLIDRegex = regexp.MustCompile(uLIDRegexString) uLIDRegex = regexp.MustCompile(uLIDRegexString)
md4Regex = regexp.MustCompile(md4RegexString)
md5Regex = regexp.MustCompile(md5RegexString)
sha256Regex = regexp.MustCompile(sha256RegexString)
sha384Regex = regexp.MustCompile(sha384RegexString)
sha512Regex = regexp.MustCompile(sha512RegexString)
ripemd128Regex = regexp.MustCompile(ripemd128RegexString)
ripemd160Regex = regexp.MustCompile(ripemd160RegexString)
tiger128Regex = regexp.MustCompile(tiger128RegexString)
tiger160Regex = regexp.MustCompile(tiger160RegexString)
tiger192Regex = regexp.MustCompile(tiger192RegexString)
aSCIIRegex = regexp.MustCompile(aSCIIRegexString) aSCIIRegex = regexp.MustCompile(aSCIIRegexString)
printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString) printableASCIIRegex = regexp.MustCompile(printableASCIIRegexString)
multibyteRegex = regexp.MustCompile(multibyteRegexString) multibyteRegex = regexp.MustCompile(multibyteRegexString)

View file

@ -82,7 +82,7 @@ BEGIN:
fld := namespace fld := namespace
var ns string var ns string
if typ != timeType { if !typ.ConvertibleTo(timeType) {
idx := strings.Index(namespace, namespaceSeparator) idx := strings.Index(namespace, namespaceSeparator)

View file

@ -164,7 +164,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
typ = current.Type() typ = current.Type()
if typ != timeType { if !typ.ConvertibleTo(timeType) {
if ct != nil { if ct != nil {
@ -355,6 +355,10 @@ OUTER:
v.ct = ct v.ct = ct
if ct.fn(ctx, v) { if ct.fn(ctx, v) {
if ct.isBlockEnd {
ct = ct.next
continue OUTER
}
// drain rest of the 'or' values, then continue or leave // drain rest of the 'or' values, then continue or leave
for { for {
@ -368,6 +372,11 @@ OUTER:
if ct.typeof != typeOr { if ct.typeof != typeOr {
continue OUTER continue OUTER
} }
if ct.isBlockEnd {
ct = ct.next
continue OUTER
}
} }
} }

View file

@ -33,6 +33,8 @@ const (
excludedWithoutTag = "excluded_without" excludedWithoutTag = "excluded_without"
excludedWithTag = "excluded_with" excludedWithTag = "excluded_with"
excludedWithAllTag = "excluded_with_all" excludedWithAllTag = "excluded_with_all"
excludedIfTag = "excluded_if"
excludedUnlessTag = "excluded_unless"
skipValidationTag = "-" skipValidationTag = "-"
diveTag = "dive" diveTag = "dive"
keysTag = "keys" keysTag = "keys"
@ -84,6 +86,7 @@ type Validate struct {
aliases map[string]string aliases map[string]string
validations map[string]internalValidationFuncWrapper validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
rules map[reflect.Type]map[string]string
tagCache *tagCache tagCache *tagCache
structCache *structCache structCache *structCache
} }
@ -120,7 +123,7 @@ func New() *Validate {
switch k { switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag: excludedIfTag, excludedUnlessTag, excludedWithTag, excludedWithAllTag, excludedWithoutTag, excludedWithoutAllTag:
_ = v.registerValidation(k, wrapFunc(val), true, true) _ = v.registerValidation(k, wrapFunc(val), true, true)
default: default:
// no need to error check here, baked in will always be valid // no need to error check here, baked in will always be valid
@ -152,15 +155,24 @@ func (v *Validate) SetTagName(name string) {
func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{}, rules map[string]interface{}) map[string]interface{} {
errs := make(map[string]interface{}) errs := make(map[string]interface{})
for field, rule := range rules { for field, rule := range rules {
if reflect.ValueOf(rule).Kind() == reflect.Map && reflect.ValueOf(data[field]).Kind() == reflect.Map { if ruleObj, ok := rule.(map[string]interface{}); ok {
err := v.ValidateMapCtx(ctx, data[field].(map[string]interface{}), rule.(map[string]interface{})) if dataObj, ok := data[field].(map[string]interface{}); ok {
err := v.ValidateMapCtx(ctx, dataObj, ruleObj)
if len(err) > 0 { if len(err) > 0 {
errs[field] = err errs[field] = err
} }
} else if reflect.ValueOf(rule).Kind() == reflect.Map { } else if dataObjs, ok := data[field].([]map[string]interface{}); ok {
errs[field] = errors.New("The field: '" + field + "' is not a map to dive") for _, obj := range dataObjs {
err := v.ValidateMapCtx(ctx, obj, ruleObj)
if len(err) > 0 {
errs[field] = err
}
}
} else { } else {
err := v.VarCtx(ctx, data[field], rule.(string)) errs[field] = errors.New("The field: '" + field + "' is not a map to dive")
}
} else if ruleStr, ok := rule.(string); ok {
err := v.VarCtx(ctx, data[field], ruleStr)
if err != nil { if err != nil {
errs[field] = err errs[field] = err
} }
@ -169,7 +181,7 @@ func (v Validate) ValidateMapCtx(ctx context.Context, data map[string]interface{
return errs return errs
} }
// ValidateMap validates map data form a map of tags // ValidateMap validates map data from a map of tags
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{} { func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{} {
return v.ValidateMapCtx(context.Background(), data, rules) return v.ValidateMapCtx(context.Background(), data, rules)
} }
@ -180,6 +192,7 @@ func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]int
// //
// validate.RegisterTagNameFunc(func(fld reflect.StructField) string { // validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
// name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] // name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
// // skip if tag key says it should be ignored
// if name == "-" { // if name == "-" {
// return "" // return ""
// } // }
@ -271,6 +284,34 @@ func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...i
} }
} }
// RegisterStructValidationMapRules registers validate map rules.
// Be aware that map validation rules supersede those defined on a/the struct if present.
//
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterStructValidationMapRules(rules map[string]string, types ...interface{}) {
if v.rules == nil {
v.rules = make(map[reflect.Type]map[string]string)
}
deepCopyRules := make(map[string]string)
for i, rule := range rules {
deepCopyRules[i] = rule
}
for _, t := range types {
typ := reflect.TypeOf(t)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Struct {
continue
}
v.rules[typ] = deepCopyRules
}
}
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
// //
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
@ -331,7 +372,7 @@ func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) {
val = val.Elem() val = val.Elem()
} }
if val.Kind() != reflect.Struct || val.Type() == timeType { if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) {
return &InvalidValidationError{Type: reflect.TypeOf(s)} return &InvalidValidationError{Type: reflect.TypeOf(s)}
} }
@ -376,7 +417,7 @@ func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn Filt
val = val.Elem() val = val.Elem()
} }
if val.Kind() != reflect.Struct || val.Type() == timeType { if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) {
return &InvalidValidationError{Type: reflect.TypeOf(s)} return &InvalidValidationError{Type: reflect.TypeOf(s)}
} }
@ -424,7 +465,7 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
val = val.Elem() val = val.Elem()
} }
if val.Kind() != reflect.Struct || val.Type() == timeType { if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) {
return &InvalidValidationError{Type: reflect.TypeOf(s)} return &InvalidValidationError{Type: reflect.TypeOf(s)}
} }
@ -514,7 +555,7 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ..
val = val.Elem() val = val.Elem()
} }
if val.Kind() != reflect.Struct || val.Type() == timeType { if val.Kind() != reflect.Struct || val.Type().ConvertibleTo(timeType) {
return &InvalidValidationError{Type: reflect.TypeOf(s)} return &InvalidValidationError{Type: reflect.TypeOf(s)}
} }

View file

@ -56,7 +56,7 @@ func (c RegisteredClaims) Valid() error {
// default value in Go, let's not fail the verification for them. // default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) { if !c.VerifyExpiresAt(now, false) {
delta := now.Sub(c.ExpiresAt.Time) delta := now.Sub(c.ExpiresAt.Time)
vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }
@ -149,7 +149,7 @@ func (c StandardClaims) Valid() error {
// default value in Go, let's not fail the verification for them. // default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now, false) { if !c.VerifyExpiresAt(now, false) {
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("%s by %v", delta, ErrTokenExpired) vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }

View file

@ -1,3 +1,7 @@
module github.com/golang-jwt/jwt/v4 module github.com/golang-jwt/jwt/v4
go 1.15 go 1.16
retract (
v4.4.0 // Contains a backwards incompatible change to the Claims interface.
)

View file

@ -1,179 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"fmt"
"strings"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
anypb "github.com/golang/protobuf/ptypes/any"
)
const urlPrefix = "type.googleapis.com/"
// AnyMessageName returns the message name contained in an anypb.Any message.
// Most type assertions should use the Is function instead.
//
// Deprecated: Call the any.MessageName method instead.
func AnyMessageName(any *anypb.Any) (string, error) {
name, err := anyMessageName(any)
return string(name), err
}
func anyMessageName(any *anypb.Any) (protoreflect.FullName, error) {
if any == nil {
return "", fmt.Errorf("message is nil")
}
name := protoreflect.FullName(any.TypeUrl)
if i := strings.LastIndex(any.TypeUrl, "/"); i >= 0 {
name = name[i+len("/"):]
}
if !name.IsValid() {
return "", fmt.Errorf("message type url %q is invalid", any.TypeUrl)
}
return name, nil
}
// MarshalAny marshals the given message m into an anypb.Any message.
//
// Deprecated: Call the anypb.New function instead.
func MarshalAny(m proto.Message) (*anypb.Any, error) {
switch dm := m.(type) {
case DynamicAny:
m = dm.Message
case *DynamicAny:
if dm == nil {
return nil, proto.ErrNil
}
m = dm.Message
}
b, err := proto.Marshal(m)
if err != nil {
return nil, err
}
return &anypb.Any{TypeUrl: urlPrefix + proto.MessageName(m), Value: b}, nil
}
// Empty returns a new message of the type specified in an anypb.Any message.
// It returns protoregistry.NotFound if the corresponding message type could not
// be resolved in the global registry.
//
// Deprecated: Use protoregistry.GlobalTypes.FindMessageByName instead
// to resolve the message name and create a new instance of it.
func Empty(any *anypb.Any) (proto.Message, error) {
name, err := anyMessageName(any)
if err != nil {
return nil, err
}
mt, err := protoregistry.GlobalTypes.FindMessageByName(name)
if err != nil {
return nil, err
}
return proto.MessageV1(mt.New().Interface()), nil
}
// UnmarshalAny unmarshals the encoded value contained in the anypb.Any message
// into the provided message m. It returns an error if the target message
// does not match the type in the Any message or if an unmarshal error occurs.
//
// The target message m may be a *DynamicAny message. If the underlying message
// type could not be resolved, then this returns protoregistry.NotFound.
//
// Deprecated: Call the any.UnmarshalTo method instead.
func UnmarshalAny(any *anypb.Any, m proto.Message) error {
if dm, ok := m.(*DynamicAny); ok {
if dm.Message == nil {
var err error
dm.Message, err = Empty(any)
if err != nil {
return err
}
}
m = dm.Message
}
anyName, err := AnyMessageName(any)
if err != nil {
return err
}
msgName := proto.MessageName(m)
if anyName != msgName {
return fmt.Errorf("mismatched message type: got %q want %q", anyName, msgName)
}
return proto.Unmarshal(any.Value, m)
}
// Is reports whether the Any message contains a message of the specified type.
//
// Deprecated: Call the any.MessageIs method instead.
func Is(any *anypb.Any, m proto.Message) bool {
if any == nil || m == nil {
return false
}
name := proto.MessageName(m)
if !strings.HasSuffix(any.TypeUrl, name) {
return false
}
return len(any.TypeUrl) == len(name) || any.TypeUrl[len(any.TypeUrl)-len(name)-1] == '/'
}
// DynamicAny is a value that can be passed to UnmarshalAny to automatically
// allocate a proto.Message for the type specified in an anypb.Any message.
// The allocated message is stored in the embedded proto.Message.
//
// Example:
// var x ptypes.DynamicAny
// if err := ptypes.UnmarshalAny(a, &x); err != nil { ... }
// fmt.Printf("unmarshaled message: %v", x.Message)
//
// Deprecated: Use the any.UnmarshalNew method instead to unmarshal
// the any message contents into a new instance of the underlying message.
type DynamicAny struct{ proto.Message }
func (m DynamicAny) String() string {
if m.Message == nil {
return "<nil>"
}
return m.Message.String()
}
func (m DynamicAny) Reset() {
if m.Message == nil {
return
}
m.Message.Reset()
}
func (m DynamicAny) ProtoMessage() {
return
}
func (m DynamicAny) ProtoReflect() protoreflect.Message {
if m.Message == nil {
return nil
}
return dynamicAny{proto.MessageReflect(m.Message)}
}
type dynamicAny struct{ protoreflect.Message }
func (m dynamicAny) Type() protoreflect.MessageType {
return dynamicAnyType{m.Message.Type()}
}
func (m dynamicAny) New() protoreflect.Message {
return dynamicAnyType{m.Message.Type()}.New()
}
func (m dynamicAny) Interface() protoreflect.ProtoMessage {
return DynamicAny{proto.MessageV1(m.Message.Interface())}
}
type dynamicAnyType struct{ protoreflect.MessageType }
func (t dynamicAnyType) New() protoreflect.Message {
return dynamicAny{t.MessageType.New()}
}
func (t dynamicAnyType) Zero() protoreflect.Message {
return dynamicAny{t.MessageType.Zero()}
}

View file

@ -1,62 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: github.com/golang/protobuf/ptypes/any/any.proto
package any
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
)
// Symbols defined in public import of google/protobuf/any.proto.
type Any = anypb.Any
var File_github_com_golang_protobuf_ptypes_any_any_proto protoreflect.FileDescriptor
var file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = []byte{
0x0a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x2b, 0x5a, 0x29,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e,
0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65,
0x73, 0x2f, 0x61, 0x6e, 0x79, 0x3b, 0x61, 0x6e, 0x79, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = []interface{}{}
var file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_github_com_golang_protobuf_ptypes_any_any_proto_init() }
func file_github_com_golang_protobuf_ptypes_any_any_proto_init() {
if File_github_com_golang_protobuf_ptypes_any_any_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes,
DependencyIndexes: file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs,
}.Build()
File_github_com_golang_protobuf_ptypes_any_any_proto = out.File
file_github_com_golang_protobuf_ptypes_any_any_proto_rawDesc = nil
file_github_com_golang_protobuf_ptypes_any_any_proto_goTypes = nil
file_github_com_golang_protobuf_ptypes_any_any_proto_depIdxs = nil
}

View file

@ -1,10 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ptypes provides functionality for interacting with well-known types.
//
// Deprecated: Well-known types have specialized functionality directly
// injected into the generated packages for each message type.
// See the deprecation notice for each function for the suggested alternative.
package ptypes

View file

@ -1,76 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
durationpb "github.com/golang/protobuf/ptypes/duration"
)
// Range of google.protobuf.Duration as specified in duration.proto.
// This is about 10,000 years in seconds.
const (
maxSeconds = int64(10000 * 365.25 * 24 * 60 * 60)
minSeconds = -maxSeconds
)
// Duration converts a durationpb.Duration to a time.Duration.
// Duration returns an error if dur is invalid or overflows a time.Duration.
//
// Deprecated: Call the dur.AsDuration and dur.CheckValid methods instead.
func Duration(dur *durationpb.Duration) (time.Duration, error) {
if err := validateDuration(dur); err != nil {
return 0, err
}
d := time.Duration(dur.Seconds) * time.Second
if int64(d/time.Second) != dur.Seconds {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
if dur.Nanos != 0 {
d += time.Duration(dur.Nanos) * time.Nanosecond
if (d < 0) != (dur.Nanos < 0) {
return 0, fmt.Errorf("duration: %v is out of range for time.Duration", dur)
}
}
return d, nil
}
// DurationProto converts a time.Duration to a durationpb.Duration.
//
// Deprecated: Call the durationpb.New function instead.
func DurationProto(d time.Duration) *durationpb.Duration {
nanos := d.Nanoseconds()
secs := nanos / 1e9
nanos -= secs * 1e9
return &durationpb.Duration{
Seconds: int64(secs),
Nanos: int32(nanos),
}
}
// validateDuration determines whether the durationpb.Duration is valid
// according to the definition in google/protobuf/duration.proto.
// A valid durpb.Duration may still be too large to fit into a time.Duration
// Note that the range of durationpb.Duration is about 10,000 years,
// while the range of time.Duration is about 290 years.
func validateDuration(dur *durationpb.Duration) error {
if dur == nil {
return errors.New("duration: nil Duration")
}
if dur.Seconds < minSeconds || dur.Seconds > maxSeconds {
return fmt.Errorf("duration: %v: seconds out of range", dur)
}
if dur.Nanos <= -1e9 || dur.Nanos >= 1e9 {
return fmt.Errorf("duration: %v: nanos out of range", dur)
}
// Seconds and Nanos must have the same sign, unless d.Nanos is zero.
if (dur.Seconds < 0 && dur.Nanos > 0) || (dur.Seconds > 0 && dur.Nanos < 0) {
return fmt.Errorf("duration: %v: seconds and nanos have different signs", dur)
}
return nil
}

View file

@ -1,63 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: github.com/golang/protobuf/ptypes/duration/duration.proto
package duration
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
reflect "reflect"
)
// Symbols defined in public import of google/protobuf/duration.proto.
type Duration = durationpb.Duration
var File_github_com_golang_protobuf_ptypes_duration_duration_proto protoreflect.FileDescriptor
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = []byte{
0x0a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c,
0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x35, 0x5a, 0x33, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73,
0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = []interface{}{}
var file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() }
func file_github_com_golang_protobuf_ptypes_duration_duration_proto_init() {
if File_github_com_golang_protobuf_ptypes_duration_duration_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes,
DependencyIndexes: file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs,
}.Build()
File_github_com_golang_protobuf_ptypes_duration_duration_proto = out.File
file_github_com_golang_protobuf_ptypes_duration_duration_proto_rawDesc = nil
file_github_com_golang_protobuf_ptypes_duration_duration_proto_goTypes = nil
file_github_com_golang_protobuf_ptypes_duration_duration_proto_depIdxs = nil
}

View file

@ -1,112 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ptypes
import (
"errors"
"fmt"
"time"
timestamppb "github.com/golang/protobuf/ptypes/timestamp"
)
// Range of google.protobuf.Duration as specified in timestamp.proto.
const (
// Seconds field of the earliest valid Timestamp.
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
minValidSeconds = -62135596800
// Seconds field just after the latest valid Timestamp.
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
maxValidSeconds = 253402300800
)
// Timestamp converts a timestamppb.Timestamp to a time.Time.
// It returns an error if the argument is invalid.
//
// Unlike most Go functions, if Timestamp returns an error, the first return
// value is not the zero time.Time. Instead, it is the value obtained from the
// time.Unix function when passed the contents of the Timestamp, in the UTC
// locale. This may or may not be a meaningful time; many invalid Timestamps
// do map to valid time.Times.
//
// A nil Timestamp returns an error. The first return value in that case is
// undefined.
//
// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead.
func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
// Don't return the zero value on error, because corresponds to a valid
// timestamp. Instead return whatever time.Unix gives us.
var t time.Time
if ts == nil {
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
} else {
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
}
return t, validateTimestamp(ts)
}
// TimestampNow returns a google.protobuf.Timestamp for the current time.
//
// Deprecated: Call the timestamppb.Now function instead.
func TimestampNow() *timestamppb.Timestamp {
ts, err := TimestampProto(time.Now())
if err != nil {
panic("ptypes: time.Now() out of Timestamp range")
}
return ts
}
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
// It returns an error if the resulting Timestamp is invalid.
//
// Deprecated: Call the timestamppb.New function instead.
func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
ts := &timestamppb.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
if err := validateTimestamp(ts); err != nil {
return nil, err
}
return ts, nil
}
// TimestampString returns the RFC 3339 string for valid Timestamps.
// For invalid Timestamps, it returns an error message in parentheses.
//
// Deprecated: Call the ts.AsTime method instead,
// followed by a call to the Format method on the time.Time value.
func TimestampString(ts *timestamppb.Timestamp) string {
t, err := Timestamp(ts)
if err != nil {
return fmt.Sprintf("(%v)", err)
}
return t.Format(time.RFC3339Nano)
}
// validateTimestamp determines whether a Timestamp is valid.
// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
// and has a Nanos field in the range [0, 1e9).
//
// If the Timestamp is valid, validateTimestamp returns nil.
// Otherwise, it returns an error that describes the problem.
//
// Every valid Timestamp can be represented by a time.Time,
// but the converse is not true.
func validateTimestamp(ts *timestamppb.Timestamp) error {
if ts == nil {
return errors.New("timestamp: nil Timestamp")
}
if ts.Seconds < minValidSeconds {
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
}
if ts.Seconds >= maxValidSeconds {
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
}
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
}
return nil
}

View file

@ -1,5 +1,29 @@
# Changelog # Changelog
## v4.7.2 - 2022-03-16
**Fixes**
* Fix nil pointer exception when calling Start again after address binding error [#2131](https://github.com/labstack/echo/pull/2131)
* Fix CSRF middleware not being able to extract token from multipart/form-data form [#2136](https://github.com/labstack/echo/pull/2136)
* Fix Timeout middleware write race [#2126](https://github.com/labstack/echo/pull/2126)
**Enhancements**
* Recover middleware should not log panic for aborted handler [#2134](https://github.com/labstack/echo/pull/2134)
## v4.7.1 - 2022-03-13
**Fixes**
* Fix `e.Static`, `.File()`, `c.Attachment()` being picky with paths starting with `./`, `../` and `/` after 4.7.0 introduced echo.Filesystem support (Go1.16+) [#2123](https://github.com/labstack/echo/pull/2123)
**Enhancements**
* Remove some unused code [#2116](https://github.com/labstack/echo/pull/2116)
## v4.7.0 - 2022-03-01 ## v4.7.0 - 2022-03-01
**Enhancements** **Enhancements**

View file

@ -246,7 +246,7 @@ const (
const ( const (
// Version of Echo // Version of Echo
Version = "4.7.0" Version = "4.7.2"
website = "https://echo.labstack.com" website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = ` banner = `
@ -732,7 +732,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return s.Serve(e.Listener) return s.Serve(e.Listener)
} }
func (e *Echo) configureServer(s *http.Server) (err error) { func (e *Echo) configureServer(s *http.Server) error {
// Setup // Setup
e.colorer.SetOutput(e.Logger.Output()) e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger s.ErrorLog = e.StdLogger
@ -747,10 +747,11 @@ func (e *Echo) configureServer(s *http.Server) (err error) {
if s.TLSConfig == nil { if s.TLSConfig == nil {
if e.Listener == nil { if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork) l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil { if err != nil {
return err return err
} }
e.Listener = l
} }
if !e.HidePort { if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@ -791,7 +792,7 @@ func (e *Echo) TLSListenerAddr() net.Addr {
} }
// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext). // StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) { func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error {
e.startupMutex.Lock() e.startupMutex.Lock()
// Setup // Setup
s := e.Server s := e.Server
@ -808,11 +809,12 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
} }
if e.Listener == nil { if e.Listener == nil {
e.Listener, err = newListener(s.Addr, e.ListenerNetwork) l, err := newListener(s.Addr, e.ListenerNetwork)
if err != nil { if err != nil {
e.startupMutex.Unlock() e.startupMutex.Unlock()
return err return err
} }
e.Listener = l
} }
if !e.HidePort { if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))

View file

@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
) )
@ -94,10 +95,12 @@ func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
} }
} }
// defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open` // defaultFS exists to preserve pre v4.7.0 behaviour where files were open by `os.Open`.
// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images` // v4.7 introduced `echo.Filesystem` field which is Go1.16+ `fs.Fs` interface.
// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break // Difference between `os.Open` and `fs.Open` is that FS does not allow opening path that start with `.`, `..` or `/`
// all old applications that rely on being able to traverse up from current executable run path. // etc. For example previously you could have `../images` in your application but `fs := os.DirFS("./")` would not
// allow you to use `fs.Open("../images")` and this would break all old applications that rely on being able to
// traverse up from current executable run path.
// NB: private because you really should use fs.FS implementation instances // NB: private because you really should use fs.FS implementation instances
type defaultFS struct { type defaultFS struct {
prefix string prefix string
@ -108,20 +111,26 @@ func newDefaultFS() *defaultFS {
dir, _ := os.Getwd() dir, _ := os.Getwd()
return &defaultFS{ return &defaultFS{
prefix: dir, prefix: dir,
fs: os.DirFS(dir), fs: nil,
} }
} }
func (fs defaultFS) Open(name string) (fs.File, error) { func (fs defaultFS) Open(name string) (fs.File, error) {
if fs.fs == nil {
return os.Open(name)
}
return fs.fs.Open(name) return fs.fs.Open(name)
} }
func subFS(currentFs fs.FS, root string) (fs.FS, error) { func subFS(currentFs fs.FS, root string) (fs.FS, error) {
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
if dFS, ok := currentFs.(*defaultFS); ok { if dFS, ok := currentFs.(*defaultFS); ok {
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS to // we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
// allow cases when root is given as `../somepath` which is not valid for fs.FS // fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
if isRelativePath(root) {
root = filepath.Join(dFS.prefix, root) root = filepath.Join(dFS.prefix, root)
}
return &defaultFS{ return &defaultFS{
prefix: root, prefix: root,
fs: os.DirFS(root), fs: os.DirFS(root),
@ -130,6 +139,21 @@ func subFS(currentFs fs.FS, root string) (fs.FS, error) {
return fs.Sub(currentFs, root) return fs.Sub(currentFs, root)
} }
func isRelativePath(path string) bool {
if path == "" {
return true
}
if path[0] == '/' {
return false
}
if runtime.GOOS == "windows" && strings.IndexByte(path, ':') != -1 {
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#file_and_directory_names
// https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
return false
}
return true
}
// MustSubFS creates sub FS from current filesystem or panic on failure. // MustSubFS creates sub FS from current filesystem or panic on failure.
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules. // Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
// //

View file

@ -168,8 +168,8 @@ func valuesFromCookie(name string) ValuesExtractor {
// valuesFromForm returns a function that extracts values from the form field. // valuesFromForm returns a function that extracts values from the form field.
func valuesFromForm(name string) ValuesExtractor { func valuesFromForm(name string) ValuesExtractor {
return func(c echo.Context) ([]string, error) { return func(c echo.Context) ([]string, error) {
if parseErr := c.Request().ParseForm(); parseErr != nil { if c.Request().Form == nil {
return nil, fmt.Errorf("valuesFromForm parse form failed: %w", parseErr) _ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does
} }
values := c.Request().Form[name] values := c.Request().Form[name]
if len(values) == 0 { if len(values) == 0 {

View file

@ -2,6 +2,7 @@ package middleware
import ( import (
"fmt" "fmt"
"net/http"
"runtime" "runtime"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -77,6 +78,9 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
if r == http.ErrAbortHandler {
panic(r)
}
err, ok := r.(error) err, ok := r.(error)
if !ok { if !ok {
err = fmt.Errorf("%v", r) err = fmt.Errorf("%v", r)

View file

@ -2,10 +2,10 @@ package middleware
import ( import (
"context" "context"
"net/http"
"time"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"net/http"
"sync"
"time"
) )
// --------------------------------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------------------------------
@ -55,9 +55,8 @@ import (
// }) // })
// //
type (
// TimeoutConfig defines the config for Timeout middleware. // TimeoutConfig defines the config for Timeout middleware.
TimeoutConfig struct { type TimeoutConfig struct {
// Skipper defines a function to skip middleware. // Skipper defines a function to skip middleware.
Skipper Skipper Skipper Skipper
@ -77,7 +76,6 @@ type (
// difference over 500microseconds (0.5millisecond) response seems to be reliable // difference over 500microseconds (0.5millisecond) response seems to be reliable
Timeout time.Duration Timeout time.Duration
} }
)
var ( var (
// DefaultTimeoutConfig is the default Timeout middleware config. // DefaultTimeoutConfig is the default Timeout middleware config.
@ -94,10 +92,17 @@ func Timeout() echo.MiddlewareFunc {
return TimeoutWithConfig(DefaultTimeoutConfig) return TimeoutWithConfig(DefaultTimeoutConfig)
} }
// TimeoutWithConfig returns a Timeout middleware with config. // TimeoutWithConfig returns a Timeout middleware with config or panics on invalid configuration.
// See: `Timeout()`.
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc { func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
// Defaults mw, err := config.ToMiddleware()
if err != nil {
panic(err)
}
return mw
}
// ToMiddleware converts Config to middleware or returns an error for invalid configuration
func (config TimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
if config.Skipper == nil { if config.Skipper == nil {
config.Skipper = DefaultTimeoutConfig.Skipper config.Skipper = DefaultTimeoutConfig.Skipper
} }
@ -108,26 +113,29 @@ func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
return next(c) return next(c)
} }
errChan := make(chan error, 1)
handlerWrapper := echoHandlerFuncWrapper{ handlerWrapper := echoHandlerFuncWrapper{
writer: &ignorableWriter{ResponseWriter: c.Response().Writer},
ctx: c, ctx: c,
handler: next, handler: next,
errChan: make(chan error, 1), errChan: errChan,
errHandler: config.OnTimeoutRouteErrorHandler, errHandler: config.OnTimeoutRouteErrorHandler,
} }
handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage) handler := http.TimeoutHandler(handlerWrapper, config.Timeout, config.ErrorMessage)
handler.ServeHTTP(c.Response().Writer, c.Request()) handler.ServeHTTP(handlerWrapper.writer, c.Request())
select { select {
case err := <-handlerWrapper.errChan: case err := <-errChan:
return err return err
default: default:
return nil return nil
} }
} }
} }, nil
} }
type echoHandlerFuncWrapper struct { type echoHandlerFuncWrapper struct {
writer *ignorableWriter
ctx echo.Context ctx echo.Context
handler echo.HandlerFunc handler echo.HandlerFunc
errHandler func(err error, c echo.Context) errHandler func(err error, c echo.Context)
@ -160,23 +168,53 @@ func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Reques
} }
return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers return // on timeout we can not send handler error to client because `http.TimeoutHandler` has already sent headers
} }
// we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
// and should not anymore send additional headers/data
// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
if err != nil { if err != nil {
// Error must be written into Writer created in `http.TimeoutHandler` so to get Response into `commited` state. // This is needed as `http.TimeoutHandler` will write status code by itself on error and after that our tries to write
// So call global error handler to write error to the client. This is needed or `http.TimeoutHandler` will send // status code will not work anymore as Echo.Response thinks it has been already "committed" and further writes
// status code by itself and after that our tries to write status code will not work anymore and/or create errors in // create errors in log about `superfluous response.WriteHeader call from`
// log about `superfluous response.WriteHeader call from` t.writer.Ignore(true)
t.ctx.Error(err) t.ctx.Response().Writer = originalWriter // make sure we restore writer before we signal original coroutine about the error
// we pass error from handler to middlewares up in handler chain to act on it if needed. But this means that // we pass error from handler to middlewares up in handler chain to act on it if needed.
// global error handler is probably be called twice as `t.ctx.Error` already does that.
// NB: later call of the global error handler or middlewares will not take any effect, as echo.Response will be
// already marked as `committed` because we called global error handler above.
t.ctx.Response().Writer = originalWriter // make sure we restore before we signal original coroutine about the error
t.errChan <- err t.errChan <- err
return return
} }
// we restore original writer only for cases we did not timeout. On timeout we have already sent response to client
// and should not anymore send additional headers/data
// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
t.ctx.Response().Writer = originalWriter t.ctx.Response().Writer = originalWriter
} }
// ignorableWriter is ResponseWriter implementations that allows us to mark writer to ignore further write calls. This
// is handy in cases when you do not have direct control of code being executed (3rd party middleware) but want to make
// sure that external code will not be able to write response to the client.
// Writer is coroutine safe for writes.
type ignorableWriter struct {
http.ResponseWriter
lock sync.Mutex
ignoreWrites bool
}
func (w *ignorableWriter) Ignore(ignore bool) {
w.lock.Lock()
w.ignoreWrites = ignore
w.lock.Unlock()
}
func (w *ignorableWriter) WriteHeader(code int) {
w.lock.Lock()
defer w.lock.Unlock()
if w.ignoreWrites {
return
}
w.ResponseWriter.WriteHeader(code)
}
func (w *ignorableWriter) Write(b []byte) (int, error) {
w.lock.Lock()
defer w.lock.Unlock()
if w.ignoreWrites {
return len(b), nil
}
return w.ResponseWriter.Write(b)
}

116
vendor/github.com/lufia/plan9stats/disk.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package stats
import (
"bufio"
"bytes"
"context"
"os"
"path/filepath"
"strings"
)
// Storage represents /dev/sdXX/ctl.
type Storage struct {
Name string
Model string
Capacity int64
Partitions []*Partition
}
// Partition represents a part of /dev/sdXX/ctl.
type Partition struct {
Name string
Start uint64
End uint64
}
func ReadStorages(ctx context.Context, opts ...Option) ([]*Storage, error) {
cfg := newConfig(opts...)
sdctl := filepath.Join(cfg.rootdir, "/dev/sdctl")
f, err := os.Open(sdctl)
if err != nil {
return nil, err
}
defer f.Close()
var a []*Storage
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := bytes.Split(scanner.Bytes(), delim)
if len(fields) == 0 {
continue
}
exp := string(fields[0]) + "*"
if !strings.HasPrefix(exp, "sd") {
continue
}
dir := filepath.Join(cfg.rootdir, "/dev", exp)
m, err := filepath.Glob(dir)
if err != nil {
return nil, err
}
for _, dir := range m {
s, err := readStorage(dir)
if err != nil {
return nil, err
}
a = append(a, s)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return a, nil
}
func readStorage(dir string) (*Storage, error) {
ctl := filepath.Join(dir, "ctl")
f, err := os.Open(ctl)
if err != nil {
return nil, err
}
defer f.Close()
var s Storage
s.Name = filepath.Base(dir)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Bytes()
switch {
case bytes.HasPrefix(line, []byte("inquiry ")):
s.Model = string(bytes.TrimSpace(line[7:]))
case bytes.HasPrefix(line, []byte("geometry ")):
fields := bytes.Split(line, delim)
if len(fields) < 3 {
continue
}
var p intParser
sec := p.ParseInt64(string(fields[1]), 10)
size := p.ParseInt64(string(fields[2]), 10)
if err := p.Err(); err != nil {
return nil, err
}
s.Capacity = sec * size
case bytes.HasPrefix(line, []byte("part ")):
fields := bytes.Split(line, delim)
if len(fields) < 4 {
continue
}
var p intParser
start := p.ParseUint64(string(fields[2]), 10)
end := p.ParseUint64(string(fields[3]), 10)
if err := p.Err(); err != nil {
return nil, err
}
s.Partitions = append(s.Partitions, &Partition{
Name: string(fields[1]),
Start: start,
End: end,
})
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return &s, nil
}

View file

@ -2,4 +2,4 @@ module github.com/lufia/plan9stats
go 1.16 go 1.16
require github.com/google/go-cmp v0.5.7 require github.com/google/go-cmp v0.5.8

View file

@ -1,4 +1,2 @@
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

Some files were not shown because too many files have changed in this diff Show more