diff --git a/.github/docs/openapi2conv.txt b/.github/docs/openapi2conv.txt index e24925aa3..2930594d7 100644 --- a/.github/docs/openapi2conv.txt +++ b/.github/docs/openapi2conv.txt @@ -19,12 +19,12 @@ func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *ope func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter) func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements -func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) +func FromV3SecurityScheme(ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) func ToV3(doc2 *openapi2.T) (*openapi3.T, error) ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers -func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) +func ToV3Operation(components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Parameter, consumes []string) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, map[string]*openapi3.SchemaRef, error) func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) func ToV3Ref(ref string) string diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt index 449950500..2dd82ecf5 100644 --- a/.github/docs/openapi3.txt +++ b/.github/docs/openapi3.txt @@ -2,9 +2,32 @@ package openapi3 // import "github.com/getkin/kin-openapi/openapi3" Package openapi3 parses and writes OpenAPI 3 specification documents. -See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md +Supports both OpenAPI 3.0 and OpenAPI 3.1: + - OpenAPI 3.0.x: + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md + - OpenAPI 3.1.x: + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md -Code generated by go generate; DO NOT EDIT. +OpenAPI 3.1 Features: + - Type arrays with null support (e.g., ["string", "null"]) + - JSON Schema 2020-12 keywords (const, examples, prefixItems, etc.) + - Webhooks for defining callback operations + - JSON Schema dialect specification + - SPDX license identifiers + +The implementation maintains 100% backward compatibility with OpenAPI 3.0. + +For OpenAPI 3.1 validation, use the JSON Schema 2020-12 validator option: + + schema.VisitJSON(value, openapi3.EnableJSONSchema2020()) + +Version detection is available via helper methods: + + if doc.IsOpenAPI31OrLater() { + // Handle OpenAPI 3.1 specific features + } + +Code generated by go generate using refs.tmpl; DO NOT EDIT refs.go. CONSTANTS @@ -115,7 +138,7 @@ func DefaultRefNameResolver(doc *T, ref ComponentRef) string - Cutting the "#/components/" part. - Cutting the file extensions (.yaml/.json) from documents. - Trimming the common directory with the root spec. - - Replace invalid characters with with underscores. + - Replace invalid characters with underscores. This is an injective mapping over a "reasonable" amount of the possible openapi spec domain space but is not perfect. There might be edge cases. @@ -229,19 +252,50 @@ func WithValidationOptions(ctx context.Context, opts ...ValidationOption) contex TYPES -type AdditionalProperties struct { +type APIKeyInInvalidError struct { + // Value is the rejected `in:` value (empty when the field was + // omitted, otherwise the bad value e.g. "body"). + Value string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + APIKeyInInvalidError clusters "apiKey should have 'in'. It can be 'query', + 'header' or 'cookie', not X" failures. Fires when an apiKey security scheme + either omits `in:` or sets it to a value outside {query, header, cookie}. + Carries the rejected value so callers can render or filter; empty string + means the field was missing entirely. + +func (e *APIKeyInInvalidError) Error() string + +type APIKeySecuritySchemeNameRequired struct{ ValidationError } + +func (e *APIKeySecuritySchemeNameRequired) As(target any) bool + +type AdditionalProperties = BoolSchema + AdditionalProperties is a type alias for BoolSchema, kept for backward + compatibility. + +type AnchorFieldFor31Plus struct{ ValidationError } + +func (e *AnchorFieldFor31Plus) As(target any) bool + +type BoolSchema struct { Has *bool Schema *SchemaRef } + BoolSchema represents a JSON Schema keyword that can be either a boolean + or a schema object. Used for additionalProperties, unevaluatedProperties, + and unevaluatedItems. -func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) - MarshalJSON returns the JSON encoding of AdditionalProperties. +func (bs BoolSchema) MarshalJSON() ([]byte, error) + MarshalJSON returns the JSON encoding of BoolSchema. -func (addProps AdditionalProperties) MarshalYAML() (any, error) - MarshalYAML returns the YAML encoding of AdditionalProperties. +func (bs BoolSchema) MarshalYAML() (any, error) + MarshalYAML returns the YAML encoding of BoolSchema. -func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error - UnmarshalJSON sets AdditionalProperties to a copy of data. +func (bs *BoolSchema) UnmarshalJSON(data []byte) error + UnmarshalJSON sets BoolSchema to a copy of data. type Callback struct { Extensions map[string]any `json:"-" yaml:"-"` @@ -265,6 +319,9 @@ func (callback Callback) JSONLookup(token string) (any, error) JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (callback *Callback) Keys() []string + Keys returns the callback keys in a fixed order + func (callback *Callback) Len() int Len returns the amount of keys in callback excluding callback.Extensions. @@ -331,7 +388,7 @@ func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) er Validate returns an error if CallbackRef does not comply with the OpenAPI spec. -type Callbacks map[string]*CallbackRef +type Callbacks map[string]*CallbackRef // Callbacks represents components' named callbacks func (m Callbacks) JSONLookup(token string) (any, error) JSONLookup implements @@ -340,12 +397,33 @@ func (m Callbacks) JSONLookup(token string) (any, error) func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets Callbacks to a copy of data. +type CommentFieldFor31Plus struct{ ValidationError } + +func (e *CommentFieldFor31Plus) As(target any) bool + type ComponentRef interface { RefString() string RefPath() *url.URL CollectionName() string } +type ComponentValidationError struct { + // Section is the lowercase singular form of the component bucket + // ("schema", "parameter", "header", "request body", "response", + // "security scheme", "example", "link", "callback"). + Section string + // Name is the component map key. + Name string + Cause error +} + ComponentValidationError wraps validation errors inside the Components + container, carrying which sub-section (Schemas, Parameters, etc.) and which + component name failed. + +func (e *ComponentValidationError) Error() string + +func (e *ComponentValidationError) Unwrap() error + type Components struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -374,10 +452,29 @@ func (components Components) MarshalYAML() (any, error) func (components *Components) UnmarshalJSON(data []byte) error UnmarshalJSON sets Components to a copy of data. -func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) +func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Components does not comply with the OpenAPI spec. +type ConflictingPathsError struct { + // Path1 / Path2 are the two conflicting path keys, in document + // order. + Path1 string + Path2 string + // Origin is the source location of the paths object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + ConflictingPathsError clusters "conflicting paths X and Y" failures. + Fires when two path keys normalize to the same template (e.g. "/users/{a}" + and "/users/{b}" both normalize to "/users/{}"). + +func (e *ConflictingPathsError) Error() string + +type ConstFieldFor31Plus struct{ ValidationError } + +func (e *ConstFieldFor31Plus) As(target any) bool + type Contact struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -401,6 +498,10 @@ func (contact *Contact) UnmarshalJSON(data []byte) error func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Contact does not comply with the OpenAPI spec. +type ContainsFieldFor31Plus struct{ ValidationError } + +func (e *ContainsFieldFor31Plus) As(target any) bool + type Content map[string]*MediaType Content is specified by OpenAPI/Swagger 3.0 standard. @@ -426,6 +527,30 @@ func (content *Content) UnmarshalJSON(data []byte) (err error) func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Content does not comply with the OpenAPI spec. +type ContentEncodingFieldFor31Plus struct{ ValidationError } + +func (e *ContentEncodingFieldFor31Plus) As(target any) bool + +type ContentMediaTypeFieldFor31Plus struct{ ValidationError } + +func (e *ContentMediaTypeFieldFor31Plus) As(target any) bool + +type ContentSchemaFieldFor31Plus struct{ ValidationError } + +func (e *ContentSchemaFieldFor31Plus) As(target any) bool + +type DefsFieldFor31Plus struct{ ValidationError } + +func (e *DefsFieldFor31Plus) As(target any) bool + +type DependentRequiredFieldFor31Plus struct{ ValidationError } + +func (e *DependentRequiredFieldFor31Plus) As(target any) bool + +type DependentSchemasFieldFor31Plus struct{ ValidationError } + +func (e *DependentSchemasFieldFor31Plus) As(target any) bool + type Discriminator struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -449,6 +574,69 @@ func (discriminator *Discriminator) Validate(ctx context.Context, opts ...Valida Validate returns an error if Discriminator does not comply with the OpenAPI spec. +type DuplicateOperationIDError struct { + // OperationID is the duplicated operationId value. + OperationID string + // Endpoint1 / Endpoint2 are the two offending endpoints, in + // deterministic order (lexicographically) for stable error messages. + Endpoint1 string + Endpoint2 string + // Origin is the source location of the second (offending) operation + // when the document was loaded with Loader.IncludeOrigin = true. The + // pre-existing Endpoint1 is implicitly fine; the duplicate landed at + // Endpoint2's site, which is the natural "go fix this" pointer. + Origin *Origin +} + DuplicateOperationIDError clusters "two operations share an operationId" + failures. operationIds must be unique across all paths in a document. + Endpoints are rendered as " " (e.g. "POST /things"). + +func (e *DuplicateOperationIDError) Error() string + +type DuplicateParameterError struct { + // In is the parameter location (e.g. "query", "path", "header"). + In string + // Name is the duplicated parameter name. + Name string + // Origin is the source location of the offending parameter when + // the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + DuplicateParameterError clusters "more than one X parameter has name Y" + failures. Fires when two parameters on an operation (or path item) share the + same In + Name combination. + +func (e *DuplicateParameterError) Error() string + +type DynamicAnchorFieldFor31Plus struct{ ValidationError } + +func (e *DynamicAnchorFieldFor31Plus) As(target any) bool + +type DynamicRefFieldFor31Plus struct{ ValidationError } + +func (e *DynamicRefFieldFor31Plus) As(target any) bool + +type EitherFieldRequiredError struct { + // Fields is the set of field names, at least one of which must be + // set (e.g. ["value", "externalValue"]). + Fields []string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + EitherFieldRequiredError clusters "at least one of these fields must be set" + failures (example.value vs externalValue, link.operationId vs operationRef). + +func (e *EitherFieldRequiredError) Error() string + +func (e *EitherFieldRequiredError) Unwrap() error + +type ElseFieldFor31Plus struct{ ValidationError } + +func (e *ElseFieldFor31Plus) As(target any) bool + type Encoding struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -491,6 +679,24 @@ type Encodings map[string]*Encoding func (encodings *Encodings) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets Encodings to a copy of data. +type ExactlyOneFieldError struct { + // Fields is the set of fields, exactly one of which must be set + // (e.g. ["content", "schema"]). + Fields []string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + ExactlyOneFieldError clusters "exactly one of these fields must be set" + failures — e.g. a parameter or header where neither content nor schema is + set, or both are. + +func (e *ExactlyOneFieldError) Error() string + +func (e *ExactlyOneFieldError) Unwrap() error + type Example struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -558,7 +764,15 @@ func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) err Validate returns an error if ExampleRef does not comply with the OpenAPI spec. -type Examples map[string]*ExampleRef +type ExampleValueExternalValueExclusive struct{ ValidationError } + +func (e *ExampleValueExternalValueExclusive) As(target any) bool + +type ExampleValueOrExternalValueRequired struct{ ValidationError } + +func (e *ExampleValueOrExternalValueRequired) As(target any) bool + +type Examples map[string]*ExampleRef // Examples represents components' named examples func (m Examples) JSONLookup(token string) (any, error) JSONLookup implements @@ -567,6 +781,35 @@ func (m Examples) JSONLookup(token string) (any, error) func (examples *Examples) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets Examples to a copy of data. +type ExamplesFieldFor31Plus struct{ ValidationError } + +func (e *ExamplesFieldFor31Plus) As(target any) bool + +type ExclusiveBound struct { + Bool *bool // For OpenAPI 3.0 style (modifier for min/max) + Value *float64 // For OpenAPI 3.1 style (actual bound value) +} + ExclusiveBound represents exclusiveMinimum/exclusiveMaximum which changed + type between OpenAPI versions. In OpenAPI 3.0 (JSON Schema draft-04): + boolean that modifies minimum/maximum In OpenAPI 3.1 (JSON Schema 2020-12): + number representing the actual exclusive bound + +func (eb ExclusiveBound) IsSet() bool + IsSet returns true if either Bool or Value is set. + +func (eb ExclusiveBound) IsTrue() bool + IsTrue returns true if the bound is set as a boolean true (OpenAPI 3.0 + style). + +func (eb ExclusiveBound) MarshalJSON() ([]byte, error) + MarshalJSON returns the JSON encoding of ExclusiveBound. + +func (eb ExclusiveBound) MarshalYAML() (any, error) + MarshalYAML returns the YAML encoding of ExclusiveBound. + +func (eb *ExclusiveBound) UnmarshalJSON(data []byte) error + UnmarshalJSON sets ExclusiveBound to a copy of data. + type ExternalDocs struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -590,6 +833,77 @@ func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) e Validate returns an error if ExternalDocs does not comply with the OpenAPI spec. +type ExternalDocsURLRequired struct{ ValidationError } + +func (e *ExternalDocsURLRequired) As(target any) bool + +type ExternalDocsURLValidationError struct { + Cause error +} + ExternalDocsURLValidationError wraps the URL parse failure on an + ExternalDocs object. + +func (e *ExternalDocsURLValidationError) Error() string + +func (e *ExternalDocsURLValidationError) Unwrap() error + +type ExtraSiblingFieldsError struct { + // Fields is the list of unexpected sibling field names. + Fields []string + // Origin is the source location of the parent object that carries + // the extra siblings when the document was loaded with + // Loader.IncludeOrigin = true. + Origin *Origin +} + ExtraSiblingFieldsError clusters "unexpected sibling fields" failures. Most + commonly this fires when fields appear alongside a $ref that the OpenAPI + spec doesn't allow there, or as unknown keys on objects whose only permitted + extras are `x-` extensions. Carries the offending field names so callers can + render or filter. + +func (e *ExtraSiblingFieldsError) Error() string + +type FieldVersionMismatchError struct { + // Field is the field name flagged (e.g. "summary", "identifier", + // "$defs", "prefixItems", "contains", ...). + Field string + // MinVersion is the minimum OpenAPI version that allows the field + // (e.g. "3.1"). + MinVersion string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil for + // document-root fields (Loader doesn't track Origin on *T) and on + // loads where origin tracking was off. + Origin *Origin +} + FieldVersionMismatchError clusters "field X is for OpenAPI >=Y" failures + (3.1+ keywords used in 3.0 documents). Carries the field name and minimum + version, wraps the per-site leaf. + +func (e *FieldVersionMismatchError) Error() string + +func (e *FieldVersionMismatchError) Unwrap() error + +type ForbiddenFieldError struct { + // Field is the name of the forbidden field (e.g. "name", "in", + // "authorizationUrl", "tokenUrl"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + ForbiddenFieldError clusters "field X must not be set in this context" + failures (header.name and header.in inside a Headers map, OAuth flow URLs + that don't apply to the chosen flow type). + +func (e *ForbiddenFieldError) Error() string + +func (e *ForbiddenFieldError) Unwrap() error + type FormatValidator[T any] interface { Validate(value T) error } @@ -632,6 +946,34 @@ func (header *Header) UnmarshalJSON(data []byte) error func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Header does not comply with the OpenAPI spec. +type HeaderContentSchemaExactlyOne struct{ ValidationError } + +func (e *HeaderContentSchemaExactlyOne) As(target any) bool + +type HeaderContentSingleEntry struct{ ValidationError } + +func (e *HeaderContentSingleEntry) As(target any) bool + +type HeaderFieldValidationError struct { + // Field is "schema" or "content". + Field string + Cause error +} + HeaderFieldValidationError wraps validation errors on a Header's `schema` or + `content` sub-objects. Field discriminates the two. + +func (e *HeaderFieldValidationError) Error() string + +func (e *HeaderFieldValidationError) Unwrap() error + +type HeaderInForbidden struct{ ValidationError } + +func (e *HeaderInForbidden) As(target any) bool + +type HeaderNameForbidden struct{ ValidationError } + +func (e *HeaderNameForbidden) As(target any) bool + type HeaderRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -673,7 +1015,7 @@ func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) erro Validate returns an error if HeaderRef does not comply with the OpenAPI spec. -type Headers map[string]*HeaderRef +type Headers map[string]*HeaderRef // Headers represents components' named headers func (m Headers) JSONLookup(token string) (any, error) JSONLookup implements @@ -682,11 +1024,20 @@ func (m Headers) JSONLookup(token string) (any, error) func (headers *Headers) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets Headers to a copy of data. +type IDFieldFor31Plus struct{ ValidationError } + +func (e *IDFieldFor31Plus) As(target any) bool + +type IfFieldFor31Plus struct{ ValidationError } + +func (e *IfFieldFor31Plus) As(target any) bool + type Info struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` - Title string `json:"title" yaml:"title"` // Required + Title string `json:"title" yaml:"title"` // Required + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` // OpenAPI >=3.1 Description string `json:"description,omitempty" yaml:"description,omitempty"` TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` @@ -695,6 +1046,8 @@ type Info struct { } Info is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object + and + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#info-object func (info Info) MarshalJSON() ([]byte, error) MarshalJSON returns the JSON encoding of Info. @@ -708,18 +1061,112 @@ func (info *Info) UnmarshalJSON(data []byte) error func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Info does not comply with the OpenAPI spec. +type InfoRequired struct{ ValidationError } + +func (e *InfoRequired) As(target any) bool + +type InfoSummaryFieldFor31Plus struct{ ValidationError } + +func (e *InfoSummaryFieldFor31Plus) As(target any) bool + +type InfoTitleRequired struct{ ValidationError } + +func (e *InfoTitleRequired) As(target any) bool + +type InfoVersionRequired struct{ ValidationError } + +func (e *InfoVersionRequired) As(target any) bool + type IntegerFormatValidator = FormatValidator[int64] IntegerFormatValidator is a type alias for FormatValidator[int64] +type InvalidHTTPSchemeError struct { + // Scheme is the rejected scheme value (e.g. "mutual", "oauth"). + Scheme string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + InvalidHTTPSchemeError clusters "security scheme of type 'http' has invalid + 'scheme' value X" failures. The OpenAPI/HTTP-auth registry accepts only + `bearer`, `basic`, `negotiate`, `digest`; this fires when an http scheme + declares anything else. + +func (e *InvalidHTTPSchemeError) Error() string + +type InvalidParameterInError struct { + // Value is the rejected `in:` value (e.g. "body", "formData"). + Value string + // Origin is the source location of the offending parameter when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + InvalidParameterInError clusters "parameter can't have 'in' value X" + failures. The OpenAPI 3.x spec accepts only `path`, `query`, `header`, + or `cookie`; this fires when a parameter declares anything else (commonly + `body`, a Swagger 2.0 leftover). + +func (e *InvalidParameterInError) Error() string + +type InvalidSecuritySchemeTypeError struct { + // Type is the rejected type value (e.g. "cookie", "saml"). + Type string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + InvalidSecuritySchemeTypeError clusters "security scheme 'type' can't be + X" failures. The OpenAPI 3.x spec accepts only `apiKey`, `http`, `oauth2`, + `openIdConnect`, and `mutualTLS` (3.1+); this fires when a security scheme + declares anything else. + +func (e *InvalidSecuritySchemeTypeError) Error() string + +type InvalidSerializationMethodError struct { + // Subject discriminates the calling surface ("media type", + // "path"/"query"/"header"/"cookie" for parameters, or "header" + // for the header.go site). + Subject string + // Style is the offending `style:` value. + Style string + // Explode is the offending `explode:` value. + Explode bool + // Origin is the source location of the offending object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + InvalidSerializationMethodError clusters "serialization method with style=X + and explode=Y is not supported by Z" failures. Fires for invalid (style, + explode) combinations on encodings, parameters, and headers. The Subject + discriminates which surface is reporting: "media type" for encoding, + the parameter location ("path", "query", etc.) for parameters and "header" + for headers. + +func (e *InvalidSerializationMethodError) Error() string + +type JSONSchemaDialectAbsoluteURIRequired struct{ ValidationError } + +func (e *JSONSchemaDialectAbsoluteURIRequired) As(target any) bool + +type JSONSchemaDialectFieldFor31Plus struct{ ValidationError } + +func (e *JSONSchemaDialectFieldFor31Plus) As(target any) bool + type License struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` Name string `json:"name" yaml:"name"` // Required URL string `json:"url,omitempty" yaml:"url,omitempty"` + + // Identifier is an SPDX license expression for the API (OpenAPI 3.1) + // Either url or identifier can be specified, not both + Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // OpenAPI >=3.1 } License is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object + and + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#license-object func (license License) MarshalJSON() ([]byte, error) MarshalJSON returns the JSON encoding of License. @@ -733,6 +1180,18 @@ func (license *License) UnmarshalJSON(data []byte) error func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if License does not comply with the OpenAPI spec. +type LicenseIdentifierFieldFor31Plus struct{ ValidationError } + +func (e *LicenseIdentifierFieldFor31Plus) As(target any) bool + +type LicenseNameRequired struct{ ValidationError } + +func (e *LicenseNameRequired) As(target any) bool + +type LicenseURLIdentifierExclusive struct{ ValidationError } + +func (e *LicenseURLIdentifierExclusive) As(target any) bool + type Link struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -759,6 +1218,14 @@ func (link *Link) UnmarshalJSON(data []byte) error func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Link does not comply with the OpenAPI spec. +type LinkOperationIDOrRefRequired struct{ ValidationError } + +func (e *LinkOperationIDOrRefRequired) As(target any) bool + +type LinkOperationIDRefExclusive struct{ ValidationError } + +func (e *LinkOperationIDRefExclusive) As(target any) bool + type LinkRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -799,7 +1266,7 @@ func (x *LinkRef) UnmarshalJSON(data []byte) error func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if LinkRef does not comply with the OpenAPI spec. -type Links map[string]*LinkRef +type Links map[string]*LinkRef // Links represents components' named links func (m Links) JSONLookup(token string) (any, error) JSONLookup implements @@ -820,6 +1287,13 @@ type Loader struct { // ReadFromURIFunc allows overriding the any file/URL reading func ReadFromURIFunc ReadFromURIFunc + // JoinFunc allows overriding how relative $ref paths are resolved against + // a base path. When set, it is called instead of the default join logic + // that uses path.Dir and path.Join. This is useful when loading specs from + // non-filesystem sources (e.g. git objects, remote archives) where the base + // path follows a different convention than filesystem paths. + JoinFunc func(basePath *url.URL, relativePath *url.URL) *url.URL + Context context.Context // Has unexported fields. @@ -857,6 +1331,13 @@ type Location struct { Line int `json:"line,omitempty" yaml:"line,omitempty"` Column int `json:"column,omitempty" yaml:"column,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` + + // EndLine and EndColumn mark the end of the block this location heads (set + // only on Origin.Key). For an operation or schema this spans the whole + // block, so a consumer can extract the entire element from its source. + // Both are zero when the underlying YAML carried no end information. + EndLine int `json:"endLine,omitempty" yaml:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty" yaml:"endColumn,omitempty"` } Location is a struct that contains the location of a field. @@ -869,6 +1350,10 @@ func (mr MappingRef) MarshalText() ([]byte, error) func (mr *MappingRef) UnmarshalText(data []byte) error +type MaxContainsFieldFor31Plus struct{ ValidationError } + +func (e *MaxContainsFieldFor31Plus) As(target any) bool + type MediaType struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -908,6 +1393,26 @@ func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType +type MediaTypeExampleExamplesExclusive struct{ ValidationError } + +func (e *MediaTypeExampleExamplesExclusive) As(target any) bool + +type MediaTypeExampleValidationError struct { + // ExampleName is the example map key. + ExampleName string + Cause error +} + MediaTypeExampleValidationError wraps validation errors on a named example + inside a MediaType.examples map. + +func (e *MediaTypeExampleValidationError) Error() string + +func (e *MediaTypeExampleValidationError) Unwrap() error + +type MinContainsFieldFor31Plus struct{ ValidationError } + +func (e *MinContainsFieldFor31Plus) As(target any) bool + type MultiError []error MultiError is a collection of errors, intended for when multiple issues need to be reported upstream @@ -923,6 +1428,26 @@ func (me MultiError) Is(target error) bool `errors.Is()` It will also return true if any of the contained errors match target +type MutuallyExclusiveFieldsError struct { + // Field1 and Field2 name the two fields the spec forbids setting + // together (e.g. "value", "externalValue"). + Field1 string + Field2 string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + MutuallyExclusiveFieldsError clusters "fields X and Y are both set, only + one is allowed" failures (example.value vs externalValue, mediaType.example + vs examples, license.url vs identifier, link.operationId vs operationRef). + Carries both field names and wraps the per-site leaf. + +func (e *MutuallyExclusiveFieldsError) Error() string + +func (e *MutuallyExclusiveFieldsError) Unwrap() error + type NewCallbackOption func(*Callback) NewCallbackOption describes options to NewCallback func @@ -972,6 +1497,52 @@ func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) e Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +type OAuthFlowAuthorizationURLForbidden struct{ ValidationError } + +func (e *OAuthFlowAuthorizationURLForbidden) As(target any) bool + +type OAuthFlowAuthorizationURLRequired struct{ ValidationError } + +func (e *OAuthFlowAuthorizationURLRequired) As(target any) bool + +type OAuthFlowFieldValidationError struct { + // Field is the offending field name ("refreshUrl" is the only + // site today; future URL fields can reuse the same wrapper). + Field string + Cause error +} + OAuthFlowFieldValidationError wraps validation errors on a specific field + inside an OAuthFlow object. Field discriminates which URL field failed. + +func (e *OAuthFlowFieldValidationError) Error() string + +func (e *OAuthFlowFieldValidationError) Unwrap() error + +type OAuthFlowScopesRequired struct{ ValidationError } + +func (e *OAuthFlowScopesRequired) As(target any) bool + +type OAuthFlowTokenURLForbidden struct{ ValidationError } + +func (e *OAuthFlowTokenURLForbidden) As(target any) bool + +type OAuthFlowTokenURLRequired struct{ ValidationError } + +func (e *OAuthFlowTokenURLRequired) As(target any) bool + +type OAuthFlowValidationError struct { + // FlowKind is one of "implicit", "password", "clientCredentials", + // "authorizationCode". + FlowKind string + Cause error +} + OAuthFlowValidationError wraps validation errors on a specific OAuth flow + inside OAuthFlows. + +func (e *OAuthFlowValidationError) Error() string + +func (e *OAuthFlowValidationError) Unwrap() error + type OAuthFlows struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -997,6 +1568,14 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +type OpenAPIVersionRequired struct{ ValidationError } + +func (e *OpenAPIVersionRequired) As(target any) bool + +type OpenIDConnectURLRequired struct{ ValidationError } + +func (e *OpenIDConnectURLRequired) As(target any) bool + type Operation struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -1062,6 +1641,25 @@ func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOpti Validate returns an error if Operation does not comply with the OpenAPI spec. +type OperationResponsesRequired struct{ ValidationError } + +func (e *OperationResponsesRequired) As(target any) bool + +type OperationValidationError struct { + Method string + Cause error +} + OperationValidationError wraps an error originating inside a specific + HTTP-method operation under a path. Method is the uppercase method (GET, + POST, etc.). + + Use errors.As(err, &ove) to extract the method from a validation error chain + without parsing the rendered message. + +func (e *OperationValidationError) Error() string + +func (e *OperationValidationError) Unwrap() error + type Origin struct { Key *Location `json:"key,omitempty" yaml:"key,omitempty"` Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"` @@ -1129,6 +1727,48 @@ func (parameter *Parameter) WithRequired(value bool) *Parameter func (parameter *Parameter) WithSchema(value *Schema) *Parameter +type ParameterContentSchemaExactlyOne struct{ ValidationError } + +func (e *ParameterContentSchemaExactlyOne) As(target any) bool + +type ParameterContentSingleEntry struct{ ValidationError } + +func (e *ParameterContentSingleEntry) As(target any) bool + +type ParameterExampleAndExamplesExclusive struct{ ValidationError } + +func (e *ParameterExampleAndExamplesExclusive) As(target any) bool + +type ParameterExampleValidationError struct { + // ExampleName is the example map key. + ExampleName string + Cause error +} + ParameterExampleValidationError wraps validation errors on a named example + inside a parameter's examples map. + +func (e *ParameterExampleValidationError) Error() string + +func (e *ParameterExampleValidationError) Unwrap() error + +type ParameterFieldValidationError struct { + // ParameterName is the parameter's `name:` value. + ParameterName string + // Field is "schema" or "content". + Field string + Cause error +} + ParameterFieldValidationError wraps validation errors on a parameter's + `schema` or `content` sub-objects. Field discriminates. + +func (e *ParameterFieldValidationError) Error() string + +func (e *ParameterFieldValidationError) Unwrap() error + +type ParameterNameRequired struct{ ValidationError } + +func (e *ParameterNameRequired) As(target any) bool + type ParameterRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -1185,7 +1825,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt Validate returns an error if Parameters does not comply with the OpenAPI spec. -type ParametersMap map[string]*ParameterRef +type ParametersMap map[string]*ParameterRef // ParametersMap represents components' named parameters func (m ParametersMap) JSONLookup(token string) (any, error) JSONLookup implements @@ -1234,6 +1874,65 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if PathItem does not comply with the OpenAPI spec. +type PathMustStartWithSlashError struct { + // Path is the offending path key (e.g. "users/{id}"). + Path string + // Origin is the source location of the paths object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + PathMustStartWithSlashError clusters "path X does not start with a forward + slash" failures. Path keys in the paths object must begin with `/`. + +func (e *PathMustStartWithSlashError) Error() string + +type PathParameterRequiredError struct { + // Param is the path-parameter name (e.g. "groupId"). + Param string + // Origin is the source location of the offending parameter when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + PathParameterRequiredError clusters "path parameter X must be required" + failures: per the OpenAPI spec, every parameter with `in: path` must be + declared with `required: true`. Carries the parameter name so callers can + render or filter by it. + +func (e *PathParameterRequiredError) Error() string + +type PathParametersError struct { + // Path is the path template (e.g. "/api/{domain}/{project}/..."). + Path string + // Method is the HTTP method (e.g. "POST"). + Method string + // Missing names path-template variables (or operation parameters) + // that don't have a corresponding declaration on the other side. + Missing []string + // Origin is the source location of the path item when the document + // was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + PathParametersError clusters "operation declares fewer/more path parameters + than appear in the path template" failures. Carries the path template, + method, and the list of missing parameter names so callers can render or + filter without parsing the message. + +func (e *PathParametersError) Error() string + +type PathValidationError struct { + Path string + Cause error +} + PathValidationError wraps an error originating inside a specific path. Path + is the path template as it appears in the document (e.g. "/users/{id}"). + + Use errors.As(err, &pve) to extract the path from a validation error chain + without parsing the rendered message. + +func (e *PathValidationError) Error() string + +func (e *PathValidationError) Unwrap() error + type Paths struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -1278,6 +1977,9 @@ func (paths Paths) JSONLookup(token string) (any, error) JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (paths *Paths) Keys() []string + Keys returns the paths keys in a fixed order + func (paths *Paths) Len() int Len returns the amount of keys in paths excluding paths.Extensions. @@ -1303,6 +2005,22 @@ func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) erro func (paths *Paths) Value(key string) *PathItem Value returns the paths for key or nil +type PathsRequired struct{ ValidationError } + +func (e *PathsRequired) As(target any) bool + +type PatternPropertiesFieldFor31Plus struct{ ValidationError } + +func (e *PatternPropertiesFieldFor31Plus) As(target any) bool + +type PrefixItemsFieldFor31Plus struct{ ValidationError } + +func (e *PrefixItemsFieldFor31Plus) As(target any) bool + +type PropertyNamesFieldFor31Plus struct{ ValidationError } + +func (e *PropertyNamesFieldFor31Plus) As(target any) bool + type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) ReadFromURIFunc defines a function which reads the contents of a resource located at a URI. @@ -1355,7 +2073,7 @@ type RegexMatcher interface { MatchString(s string) bool } -type RequestBodies map[string]*RequestBodyRef +type RequestBodies map[string]*RequestBodyRef // RequestBodies represents components' named request bodies func (m RequestBodies) JSONLookup(token string) (any, error) JSONLookup implements @@ -1410,6 +2128,10 @@ func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *Re func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody +type RequestBodyContentRequired struct{ ValidationError } + +func (e *RequestBodyContentRequired) As(target any) bool + type RequestBodyRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -1451,6 +2173,33 @@ func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec. +type RequiredFieldError struct { + // Field is the JSON-pointer-style path of the required field + // (e.g. "info.version", "license.name", "openapi", "server.url"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil for + // document-root fields (Loader doesn't track Origin on *T) and on + // loads where origin tracking was off. + Origin *Origin +} + RequiredFieldError clusters "X must be a non-empty value" failures across + the spec (info.title, info.version, license.name, the openapi version + string, server.url). Carries the field path, wraps the per-site leaf so + callers can match either: + + var rfe *RequiredFieldError + if errors.As(err, &rfe) { /* knows the field */ } + + var ivr *InfoVersionRequired + if errors.As(err, &ivr) { /* knows it's exactly info.version */ } + +func (e *RequiredFieldError) Error() string + +func (e *RequiredFieldError) Unwrap() error + type Response struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -1485,7 +2234,7 @@ func (response *Response) WithJSONSchema(schema *Schema) *Response func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response -type ResponseBodies map[string]*ResponseRef +type ResponseBodies map[string]*ResponseRef // ResponseBodies represents components' named response bodies func (m ResponseBodies) JSONLookup(token string) (any, error) JSONLookup implements @@ -1494,6 +2243,10 @@ func (m ResponseBodies) JSONLookup(token string) (any, error) func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets ResponseBodies to a copy of data. +type ResponseDescriptionRequired struct{ ValidationError } + +func (e *ResponseDescriptionRequired) As(target any) bool + type ResponseRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -1561,6 +2314,9 @@ func (responses Responses) JSONLookup(token string) (any, error) JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (responses *Responses) Keys() []string + Keys returns the responses keys in a fixed order + func (responses *Responses) Len() int Len returns the amount of keys in responses excluding responses.Extensions. @@ -1593,6 +2349,10 @@ func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOpti func (responses *Responses) Value(key string) *ResponseRef Value returns the responses for key or nil +type ResponsesNonEmptyRequired struct{ ValidationError } + +func (e *ResponsesNonEmptyRequired) As(target any) bool + type Schema struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -1613,8 +2373,8 @@ type Schema struct { // Array-related, here for struct compactness UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` // Number-related, here for struct compactness - ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` - ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + ExclusiveMin ExclusiveBound `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` // Number for v3.1+ otherwise boolean + ExclusiveMax ExclusiveBound `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` // Number for v3.1+ otherwise boolean // Properties Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` @@ -1645,9 +2405,42 @@ type Schema struct { MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` AdditionalProperties AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` + + Const any `json:"const,omitempty" yaml:"const,omitempty"` // OpenAPI >=3.1 + Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` // OpenAPI >=3.1 + PrefixItems SchemaRefs `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` // OpenAPI >=3.1 + Contains *SchemaRef `json:"contains,omitempty" yaml:"contains,omitempty"` // OpenAPI >=3.1 + MinContains *uint64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` // OpenAPI >=3.1 + MaxContains *uint64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` // OpenAPI >=3.1 + PatternProperties Schemas `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` // OpenAPI >=3.1 + DependentSchemas Schemas `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` // OpenAPI >=3.1 + PropertyNames *SchemaRef `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` // OpenAPI >=3.1 + UnevaluatedItems BoolSchema `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` // OpenAPI >=3.1 + UnevaluatedProperties BoolSchema `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"` // OpenAPI >=3.1 + + If *SchemaRef `json:"if,omitempty" yaml:"if,omitempty"` // OpenAPI >=3.1 + Then *SchemaRef `json:"then,omitempty" yaml:"then,omitempty"` // OpenAPI >=3.1 + Else *SchemaRef `json:"else,omitempty" yaml:"else,omitempty"` // OpenAPI >=3.1 + + DependentRequired map[string][]string `json:"dependentRequired,omitempty" yaml:"dependentRequired,omitempty"` // OpenAPI >=3.1 + + Defs Schemas `json:"$defs,omitempty" yaml:"$defs,omitempty"` // OpenAPI >=3.1 + SchemaDialect string `json:"$schema,omitempty" yaml:"$schema,omitempty"` // OpenAPI >=3.1 + Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` // OpenAPI >=3.1 + + SchemaID string `json:"$id,omitempty" yaml:"$id,omitempty"` // OpenAPI >=3.1 + Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"` // OpenAPI >=3.1 + DynamicRef string `json:"$dynamicRef,omitempty" yaml:"$dynamicRef,omitempty"` // OpenAPI >=3.1 + DynamicAnchor string `json:"$dynamicAnchor,omitempty" yaml:"$dynamicAnchor,omitempty"` // OpenAPI >=3.1 + + ContentMediaType string `json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"` // OpenAPI >=3.1 + ContentEncoding string `json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"` // OpenAPI >=3.1 + ContentSchema *SchemaRef `json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"` // OpenAPI >=3.1 } Schema is specified by OpenAPI/Swagger 3.0 standard. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object + and + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object func NewAllOfSchema(schemas ...*Schema) *Schema @@ -1715,6 +2508,9 @@ func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) er Validate returns an error if Schema does not comply with the OpenAPI spec. func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error + VisitJSON applies a Schema to the given data, considering opts. + To validate data against an OpenAPIv3.1+ schema, be sure to pass the + EnableJSONSchema2020() option. func (schema *Schema) VisitJSONArray(value []any) error @@ -1735,8 +2531,16 @@ func (schema *Schema) WithDefault(defaultValue any) *Schema func (schema *Schema) WithEnum(values ...any) *Schema func (schema *Schema) WithExclusiveMax(value bool) *Schema + WithExclusiveMax sets exclusiveMaximum as a boolean (OpenAPI 3.0 style). + +func (schema *Schema) WithExclusiveMaxValue(value float64) *Schema + WithExclusiveMaxValue sets exclusiveMaximum as a number (OpenAPI 3.1 style). func (schema *Schema) WithExclusiveMin(value bool) *Schema + WithExclusiveMin sets exclusiveMinimum as a boolean (OpenAPI 3.0 style). + +func (schema *Schema) WithExclusiveMinValue(value float64) *Schema + WithExclusiveMinValue sets exclusiveMinimum as a number (OpenAPI 3.1 style). func (schema *Schema) WithFormat(value string) *Schema @@ -1782,6 +2586,28 @@ func (schema *Schema) WithUniqueItems(unique bool) *Schema func (schema *Schema) WithoutAdditionalProperties() *Schema +type SchemaAdditionalPropertiesBothForms struct{ ValidationError } + +func (e *SchemaAdditionalPropertiesBothForms) As(target any) bool + +type SchemaBothFormsExclusive struct { + // Field is the name of the union-typed schema property + // (e.g. "additionalProperties", "unevaluatedItems"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + SchemaBothFormsExclusive clusters "this union-typed schema field is + set to both its boolean and schema forms simultaneously" failures — + additionalProperties, unevaluatedItems, unevaluatedProperties. + +func (e *SchemaBothFormsExclusive) Error() string + +func (e *SchemaBothFormsExclusive) Unwrap() error + type SchemaError struct { // Value is the value that failed validation. Value any @@ -1806,6 +2632,42 @@ func (err *SchemaError) JSONPointer() []string func (err SchemaError) Unwrap() error +type SchemaFieldFor31Plus struct{ ValidationError } + +func (e *SchemaFieldFor31Plus) As(target any) bool + +type SchemaItemsRequired struct{ ValidationError } + +func (e *SchemaItemsRequired) As(target any) bool + +type SchemaPatternRegexError struct { + // Pattern is the schema's `pattern:` value that failed to compile. + Pattern string + // Cause is the underlying error (a *SchemaError wrapping the + // regexp package's syntax error). Unwrap returns this so callers + // walking the error chain see the SchemaError. + Cause error + // Origin is the source location of the offending schema when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + SchemaPatternRegexError clusters "schema pattern failed to compile" + failures. Kin's regex engine is Go's RE2, which rejects Perl features + like `(?!...)` lookahead; specs that leak Perl regex patterns (common + in AWS-style auto-generated schemas) trip this. Carries the offending + pattern as a structured field for typed dispatch, while preserving the + underlying SchemaError's rendered message byte-for-byte (Error() delegates + to Cause.Error()) so existing string-based consumers and golden fixtures are + unaffected. + +func (e *SchemaPatternRegexError) Error() string + +func (e *SchemaPatternRegexError) Unwrap() error + +type SchemaReadOnlyWriteOnlyExclusive struct{ ValidationError } + +func (e *SchemaReadOnlyWriteOnlyExclusive) As(target any) bool + type SchemaRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -1856,6 +2718,27 @@ func (s SchemaRefs) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +type SchemaTypeError struct { + // Type is the rejected type value (e.g. "bool", "int", "http"). + Type string + // Origin is the source location of the offending schema when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + SchemaTypeError clusters "unsupported 'type' value" failures on a Schema. + Carries the bad type value so callers can surface it in user-facing output + and filter findings by it. + +func (e *SchemaTypeError) Error() string + +type SchemaUnevaluatedItemsBothForms struct{ ValidationError } + +func (e *SchemaUnevaluatedItemsBothForms) As(target any) bool + +type SchemaUnevaluatedPropertiesBothForms struct{ ValidationError } + +func (e *SchemaUnevaluatedPropertiesBothForms) As(target any) bool + type SchemaValidationOption func(*schemaValidationSettings) SchemaValidationOption describes options a user has when validating request / response bodies. @@ -1881,6 +2764,12 @@ func EnableFormatValidation() SchemaValidationOption validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. +func EnableJSONSchema2020() SchemaValidationOption + EnableJSONSchema2020 enables JSON Schema 2020-12 compliant validation. + This enables support for OpenAPI 3.1 and JSON Schema 2020-12 features. + When enabled, validation uses the jsonschema library instead of the built-in + validator. + func FailFast() SchemaValidationOption FailFast returns schema validation errors quicker. @@ -1932,7 +2821,39 @@ func WithStringFormatValidators(validators map[string]StringFormatValidator) Sch These validators are checked before global SchemaStringFormats and allow different validations for the same format name across different specs. -type Schemas map[string]*SchemaRef +type SchemaValueError struct { + // ValueKind identifies the schema sub-field whose value failed + // (e.g. "example", "default"). + ValueKind string + // Cause is the underlying error from schema.VisitJSON — either a + // *SchemaError or a MultiError of them. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil when + // origin tracking is off. + Origin *Origin +} + SchemaValueError clusters failures of "this schema's value doesn't + satisfy the schema's own constraints" — example, default, examples[i], etc. + checked against the schema during document validation. Wraps the underlying + error from VisitJSON (a *SchemaError or a MultiError of them) so callers can + match either: + + var sve *SchemaValueError + if errors.As(err, &sve) { /* knows ValueKind = "example" */ } + + var se *SchemaError + if errors.As(err, &se) { /* full schema-validation detail */ } + + Cause is typed as error (not *SchemaError) because VisitJSON can return + either a single SchemaError or a MultiError aggregating several. errors.As + walks both shapes transparently. + +func (e *SchemaValueError) Error() string + +func (e *SchemaValueError) Unwrap() error + +type Schemas map[string]*SchemaRef // Schemas represents components' named schemas func (m Schemas) JSONLookup(token string) (any, error) JSONLookup implements @@ -1941,6 +2862,22 @@ func (m Schemas) JSONLookup(token string) (any, error) func (schemas *Schemas) UnmarshalJSON(data []byte) (err error) UnmarshalJSON sets Schemas to a copy of data. +type SectionValidationError struct { + Section string + Cause error +} + SectionValidationError wraps an error originating inside one of the + top-level OpenAPI document sections (info, paths, components, security, + servers, tags, externalDocs, webhooks, jsonSchemaDialect). Section is the + OpenAPI field name as it appears in the document root. + + Use errors.As(err, &sve) to extract the section context from a validation + error chain without parsing the rendered message. + +func (e *SectionValidationError) Error() string + +func (e *SectionValidationError) Unwrap() error + type SecurityRequirement map[string][]string SecurityRequirement is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object @@ -1981,6 +2918,8 @@ type SecurityScheme struct { } SecurityScheme is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object + and + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object func NewCSRFSecurityScheme() *SecurityScheme @@ -2015,6 +2954,36 @@ func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme func (ss *SecurityScheme) WithType(value string) *SecurityScheme +type SecuritySchemeBearerFormatForbidden struct{ ValidationError } + +func (e *SecuritySchemeBearerFormatForbidden) As(target any) bool + +type SecuritySchemeFlowValidationError struct { + Cause error +} + SecuritySchemeFlowValidationError wraps validation errors on the outer flows + object of an oauth2 security scheme. + +func (e *SecuritySchemeFlowValidationError) Error() string + +func (e *SecuritySchemeFlowValidationError) Unwrap() error + +type SecuritySchemeFlowsForbidden struct{ ValidationError } + +func (e *SecuritySchemeFlowsForbidden) As(target any) bool + +type SecuritySchemeFlowsRequired struct{ ValidationError } + +func (e *SecuritySchemeFlowsRequired) As(target any) bool + +type SecuritySchemeInForbidden struct{ ValidationError } + +func (e *SecuritySchemeInForbidden) As(target any) bool + +type SecuritySchemeNameForbidden struct{ ValidationError } + +func (e *SecuritySchemeNameForbidden) As(target any) bool + type SecuritySchemeRef struct { // Extensions only captures fields starting with 'x-' as no other fields // are allowed by the openapi spec. @@ -2057,7 +3026,7 @@ func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOpti Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec. -type SecuritySchemes map[string]*SecuritySchemeRef +type SecuritySchemes map[string]*SecuritySchemeRef // SecuritySchemes represents components' named security schemes func (m SecuritySchemes) JSONLookup(token string) (any, error) JSONLookup implements @@ -2101,9 +3070,38 @@ func (server Server) ParameterNames() ([]string, error) func (server *Server) UnmarshalJSON(data []byte) error UnmarshalJSON sets Server to a copy of data. -func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) +func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Server does not comply with the OpenAPI spec. +type ServerURLMismatchedBraces struct{ ValidationError } + +func (e *ServerURLMismatchedBraces) As(target any) bool + +type ServerURLRequired struct{ ValidationError } + +func (e *ServerURLRequired) As(target any) bool + +type ServerURLTemplateError struct { + // URL is the server URL whose template failed validation. + URL string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + ServerURLTemplateError clusters server URL template failures — mismatched + braces and undeclared variables (template variables not matched by + Server.Variables, or vice versa). + +func (e *ServerURLTemplateError) Error() string + +func (e *ServerURLTemplateError) Unwrap() error + +type ServerURLUndeclaredVariables struct{ ValidationError } + +func (e *ServerURLUndeclaredVariables) As(target any) bool + type ServerVariable struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -2126,6 +3124,10 @@ func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...Vali Validate returns an error if ServerVariable does not comply with the OpenAPI spec. +type ServerVariableDefaultRequired struct{ ValidationError } + +func (e *ServerVariableDefaultRequired) As(target any) bool + type ServerVariables map[string]*ServerVariable ServerVariable is specified by OpenAPI/Swagger standard version 3. See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object @@ -2145,6 +3147,23 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Servers does not comply with the OpenAPI spec. +type SingleEntryContentError struct { + // Subject is the kind of object whose Content map is too large + // ("parameter", "header"). + Subject string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + SingleEntryContentError clusters "the content map must contain at most one + entry" failures (parameter.content, header.content). + +func (e *SingleEntryContentError) Error() string + +func (e *SingleEntryContentError) Unwrap() error + type SliceUniqueItemsChecker func(items []any) bool SliceUniqueItemsChecker is an function used to check if an given slice have unique items. @@ -2165,20 +3184,25 @@ func (stringMap *StringMap[V]) UnmarshalJSON(data []byte) (err error) type T struct { Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"-" yaml:"-"` - OpenAPI string `json:"openapi" yaml:"openapi"` // Required - Components *Components `json:"components,omitempty" yaml:"components,omitempty"` - Info *Info `json:"info" yaml:"info"` // Required - Paths *Paths `json:"paths" yaml:"paths"` // Required - Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` - Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` - Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + OpenAPI string `json:"openapi" yaml:"openapi"` // Required + Components *Components `json:"components,omitempty" yaml:"components,omitempty"` + Info *Info `json:"info" yaml:"info"` // Required + Paths *Paths `json:"paths" yaml:"paths"` // Required in 3.0, optional in 3.1 + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Webhooks map[string]*PathItem `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` // OpenAPI >=3.1 + JSONSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` // OpenAPI >=3.1 // Has unexported fields. } T is the root of an OpenAPI v3 document See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object + and + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object func (doc *T) AddOperation(path string, method string, operation *Operation) @@ -2204,6 +3228,16 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, Comp doc.InternalizeRefs(context.Background(), nil) +func (doc *T) IsOpenAPI30() bool + IsOpenAPI30 returns whether doc is an OpenAPI document version 3.0.x. + Returns true for 3, 3.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, ... And false + for 3.1.0, 3.2, ... and for invalid strings. + +func (doc *T) IsOpenAPI31OrLater() bool + IsOpenAPI31OrLater returns whether doc is an OpenAPI document version >=3.1. + Returns true for 3.1, 3.1.0, 3.1.1, 3.1.2, 3.2.0, ... And false for cases + where IsOpenAPI30 returns true and for invalid strings. + func (doc *T) JSONLookup(token string) (any, error) JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -2214,6 +3248,10 @@ func (doc *T) MarshalJSON() ([]byte, error) func (doc *T) MarshalYAML() (any, error) MarshalYAML returns the YAML encoding of T. +func (doc *T) OpenAPIMajorMinor() string + OpenAPIMajorMinor returns 3.y of the OpenAPI "3.y" or "3.y.z" version of the + document. Returns the empty string for invalid OpenAPI version strings. + func (doc *T) SetIntegerFormatValidator(name string, validator IntegerFormatValidator) SetIntegerFormatValidator sets a single document-scoped integer format validator. @@ -2245,6 +3283,9 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if T does not comply with the OpenAPI spec. Validations Options can be provided to modify the validation behavior. + By default, doc.OpenAPI's field dictates whether "JSON Schema Draft 2020-12" + validation is enabled. + func (doc *T) ValidateSchemaJSON(schema *Schema, value any, opts ...SchemaValidationOption) error ValidateSchemaJSON validates data against a schema using this document's format validators. This is a convenience method that automatically applies @@ -2281,22 +3322,205 @@ func (tags Tags) Get(name string) *Tag func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error Validate returns an error if Tags does not comply with the OpenAPI spec. +type ThenFieldFor31Plus struct{ ValidationError } + +func (e *ThenFieldFor31Plus) As(target any) bool + type Types []string + Types represents the type(s) of a schema. + + In OpenAPI 3.0, this is typically a single type (e.g., "string"). In OpenAPI + 3.1, it can be an array of types (e.g., ["string", "null"]). + + Serialization behavior: + - Single type: serializes as a string (e.g., "string") + - Multiple types: serializes as an array (e.g., ["string", "null"]) + - Accepts both string and array formats when unmarshaling + + Example OpenAPI 3.0 (single type): + + schema := &Schema{Type: &Types{"string"}} + // JSON: {"type": "string"} + + Example OpenAPI 3.1 (type array): + + schema := &Schema{Type: &Types{"string", "null"}} + // JSON: {"type": ["string", "null"]} func (pTypes *Types) Includes(typ string) bool + Includes returns true if the given type is included in the type array. + Returns false if types is nil. + + Example: + + types := &Types{"string", "null"} + types.Includes("string") // true + types.Includes("null") // true + types.Includes("number") // false + +func (types *Types) IncludesNull() bool + IncludesNull returns true if the type array includes "null". This is useful + for OpenAPI 3.1 where null is a first-class type. + + Example: + + types := &Types{"string", "null"} + types.IncludesNull() // true + + types = &Types{"string"} + types.IncludesNull() // false func (types *Types) Is(typ string) bool + Is returns true if the schema has exactly one type and it matches the given + type. This is useful for OpenAPI 3.0 style single-type checks. + + Example: + + types := &Types{"string"} + types.Is("string") // true + types.Is("number") // false + + types = &Types{"string", "null"} + types.Is("string") // false (multiple types) + +func (types *Types) IsEmpty() bool + IsEmpty returns true if no types are specified (nil or empty array). + When a schema has no type specified, it permits any type. + + Example: + + var nilTypes *Types + nilTypes.IsEmpty() // true + + types := &Types{} + types.IsEmpty() // true + + types = &Types{"string"} + types.IsEmpty() // false + +func (types *Types) IsMultiple() bool + IsMultiple returns true if multiple types are specified. This is an OpenAPI + 3.1 feature that enables type arrays. + + Example: + + types := &Types{"string"} + types.IsMultiple() // false + + types = &Types{"string", "null"} + types.IsMultiple() // true + +func (types *Types) IsSingle() bool + IsSingle returns true if exactly one type is specified. + + Example: + + types := &Types{"string"} + types.IsSingle() // true + + types = &Types{"string", "null"} + types.IsSingle() // false func (pTypes *Types) MarshalJSON() ([]byte, error) func (pTypes *Types) MarshalYAML() (any, error) func (types *Types) Permits(typ string) bool + Permits returns true if the given type is permitted. Returns true if types + is nil (any type allowed), otherwise checks if the type is included. + + Example: + + var nilTypes *Types + nilTypes.Permits("anything") // true (nil permits everything) + + types := &Types{"string"} + types.Permits("string") // true + types.Permits("number") // false func (types *Types) Slice() []string + Slice returns the types as a string slice. Returns nil if types is nil. + + Example: + + types := &Types{"string", "null"} + slice := types.Slice() // []string{"string", "null"} func (types *Types) UnmarshalJSON(data []byte) error +type UnevaluatedItemsFieldFor31Plus struct{ ValidationError } + +func (e *UnevaluatedItemsFieldFor31Plus) As(target any) bool + +type UnevaluatedPropertiesFieldFor31Plus struct{ ValidationError } + +func (e *UnevaluatedPropertiesFieldFor31Plus) As(target any) bool + +type UnresolvedRefError struct { + // Ref is the unresolved $ref value (e.g. "#/components/schemas/X" + // or "external.yaml#/..."). + Ref string + // Origin is the source location of the ref-bearing object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + UnresolvedRefError clusters "found unresolved ref: X" failures fired by the + loader when a $ref cannot be resolved against the loaded document. Carries + the offending ref string so callers can surface it in user-facing output and + filter findings by it. + +func (e *UnresolvedRefError) Error() string + +type ValidationError struct { + Message string +} + ValidationError is the embedded base for every typed validation error + emitted by the document validation walker (T.Validate, Info.Validate, + Paths.Validate, etc.). Four categories of typed error are exposed; pick + whichever the caller needs: + + 1. Base — *ValidationError. Catchall for "this is a validation issue, + here is the message". Reachable from any leaf via the As method that + each leaf implements. + 2. Cluster — types like *RequiredFieldError or *FieldVersionMismatchError. + Group families of related failures and expose the family-level metadata + (Field, MinVersion, ...). Wrap the underlying leaf via Unwrap, + so errors.As can still walk to the leaf. Some clusters are single-site + (e.g. *SchemaTypeError) and carry only their own fields with no separate + leaf. + 3. Leaf — one type per call site (e.g. *InfoVersionRequired, + *LicenseIdentifierFieldFor31Plus). Lets callers match an exact failure + point without string comparison. + 4. Context wrapper — types like *SectionValidationError, + *PathValidationError, *ParameterFieldValidationError. Add scope ("which + section", "which path", "which parameter") around an inner error chain + but do NOT themselves report a failure condition — the actual error + lives in Cause. Defined in validation_error_context.go; see that file's + header for the full inventory and conventions. + + All four are reachable from the same returned error through standard Go + error wrapping (errors.As, errors.Is, errors.Unwrap), so a caller that only + needs "is it a validation error?" stops at the base and a caller that wants + "is it specifically license.identifier being used in 3.0?" matches the leaf. + A caller that wants "which section did this happen in?" matches the context + wrapper and walks further for the cluster/leaf. + + A canonical error chain therefore looks like: + + ComponentValidationError{Section: "schema", Name: "Foo"} + -> RequiredFieldError{Field: "type"} + -> SchemaTypeRequired{Message: "..."} + + Context wrapper carries WHERE, cluster carries WHAT category, leaf carries + EXACTLY WHICH case. + + Backward compatibility: every site that today returns errors.New(msg) + migrates to a leaf type that embeds ValidationError with Message set to + the original string. (*ValidationError).Error() returns Message unchanged, + so existing string-matching consumers see identical output. + +func (e *ValidationError) Error() string + type ValidationOption func(options *ValidationOptions) ValidationOption allows the modification of how the OpenAPI document is validated. @@ -2332,6 +3556,20 @@ func EnableExamplesValidation() ValidationOption EnableExamplesValidation does the opposite of DisableExamplesValidation. By default, all schema examples are validated. +func EnableMultiError() ValidationOption + EnableMultiError makes Validate aggregate independent validation errors and + return them together as a MultiError instead of returning the first error + and stopping. By default, Validate is fail-fast. + + Not every validator reports more than one error yet. Some, such as Schema, + run checks that build on earlier ones, so continuing past a failure can + hit a nil dereference or produce nonsense secondary errors. We will keep + converting more validators in follow-up changes as each one is analyzed. + + To pull a specific error type out of the result, use errors.As(err, + &target). It walks into the MultiError automatically, so the same call works + whether Validate returned one error or many. + func EnableSchemaDefaultsValidation() ValidationOption EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation. By default, schema default values are @@ -2347,6 +3585,10 @@ func EnableSchemaPatternValidation() ValidationOption DisableSchemaPatternValidation. By default, schema pattern validation is enabled. +func IsOpenAPI31OrLater() ValidationOption + IsOpenAPI31OrLater enables "JSON Schema Draft 2020-12"-compliant validation + (for OpenAPI 3.1 documents). + func ProhibitExtensionsWithRef() ValidationOption ProhibitExtensionsWithRef causes the validation to return an error if extensions (fields starting with 'x-') are found as siblings for $ref @@ -2362,6 +3604,39 @@ type ValidationOptions struct { } ValidationOptions provides configuration for validating OpenAPI documents. +type WebhookNil struct{ ValidationError } + +func (e *WebhookNil) As(target any) bool + +type WebhookNilError struct { + // Name is the webhook key whose value was nil. + Name string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error +} + WebhookNilError clusters "the value at webhook key X is nil" failures from + T.Validate's webhook walk. Carries the offending key name. + +func (e *WebhookNilError) Error() string + +func (e *WebhookNilError) Unwrap() error + +type WebhookValidationError struct { + // Name is the webhook map key. + Name string + Cause error +} + WebhookValidationError wraps validation errors on a named webhook at the + document root (OpenAPI 3.1+). + +func (e *WebhookValidationError) Error() string + +func (e *WebhookValidationError) Unwrap() error + +type WebhooksFieldFor31Plus struct{ ValidationError } + +func (e *WebhooksFieldFor31Plus) As(target any) bool + type XML struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` diff --git a/.github/docs/openapi3conv.txt b/.github/docs/openapi3conv.txt new file mode 100644 index 000000000..a6975a4ed --- /dev/null +++ b/.github/docs/openapi3conv.txt @@ -0,0 +1,59 @@ +package openapi3conv // import "github.com/getkin/kin-openapi/openapi3conv" + +Package openapi3conv canonicalizes an OpenAPI 3.x document into the latest 3.x +representation in place. Schema-level constructs serialize differently between +OpenAPI 3.0 and 3.1, but represent the same semantics; this package rewrites the +3.0 forms into their 3.1 equivalents and bumps the version string to the latest +3.x patch release the package knows about. + +The OAI commits to strict compatibility for 3.x going forward (see the 3.2.1 +and 3.3.0 milestones), so a tool that handles the 3.1+ form correctly handles +all later 3.x versions correctly too. The 3.0 → 3.1 transition — the only break +in the 3.x line — is the gap this package exists to bridge. 3.1 → 3.2 (and any +future 3.x) is purely additive and requires no rewrites; the package handles +those as a version-string bump. + +Use this when a downstream consumer (diff tools, validators, code generators) +needs a single canonical representation regardless of the source spec's declared +version. + +Scope: + - In scope: 3.x → latest 3.x. + - Out of scope: any → 3.0 (downgrade is lossy by nature). + - Out of scope: cross-major upgrades (3 → 4 if/when v4 ships). Those belong + in a dedicated package mirroring openapi2conv (which converts Swagger 2.0 + documents to OpenAPI 3.0). + +Documents must be Validate()'d before calling Upgrade — passing an invalid +document is undefined behaviour. + +FUNCTIONS + +func Upgrade(doc *openapi3.T, opts ...Option) + Upgrade canonicalizes doc into the latest 3.x representation in place. + + The schema-level rewrites the walker applies (nullable → type array, + boolean exclusive bounds → numeric, example → examples) are idempotent and + convergent on the 3.1+ form. Calling Upgrade on an already-3.1 (or later) + document is a no-op aside from the version string bump. + + Cross-major upgrades (3 → 4 if/when v4 ships) are not handled here; that + belongs in a dedicated package mirroring the openapi2conv pattern. + + doc must be Validate()'d before calling Upgrade; passing an invalid document + is undefined behaviour. + +func UpgradeSchema(s *openapi3.Schema) + UpgradeSchema canonicalizes a single schema (and its descendants) in place. + Exposed for callers that need to upgrade a sub-tree rather than a full + document — e.g., a diff tool comparing isolated schemas. + + +TYPES + +type Option func(*upgradeOptions) + Option configures an Upgrade pass. See WithWriter. + +func WithWriter(w io.Writer) Option + WithWriter routes one debug line per applied rewrite to w. + diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 88b7888f5..932808f34 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,13 +6,14 @@ on: jobs: build-and-test: env: + APISGURU_COMMIT: f7207cf0a5c56081d275ebae4cf615249323385d GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GO111MODULE: 'on' CGO_ENABLED: '0' strategy: fail-fast: true matrix: - go: ['1.x'] + go: ['1.26'] os: - ubuntu-latest - windows-latest @@ -24,35 +25,24 @@ jobs: name: ${{ matrix.go }} on ${{ matrix.os }} steps: - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - - id: go-cache-paths - run: | - echo "::set-output name=go-build::$(go env GOCACHE)" - echo "::set-output name=go-mod::$(go env GOMODCACHE)" - - run: echo ${{ steps.go-cache-paths.outputs.go-build }} - - run: echo ${{ steps.go-cache-paths.outputs.go-mod }} - - - name: Go Build Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.go-build }} - key: ${{ runner.os }}-go-${{ matrix.go }}-build-${{ hashFiles('**/go.sum') }} - - - name: Go Mod Cache (go>=1.15) + - name: Test suite fixtures cache uses: actions/cache@v3 with: - path: ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-${{ matrix.go }}-mod-${{ hashFiles('**/go.sum') }} + path: | + openapi2/testdata/APIs-guru-openapi-directory-* + openapi3/testdata/APIs-guru-openapi-directory-* + key: suite-\${{ env.APISGURU_COMMIT }} - if: runner.os == 'Linux' run: sudo apt install silversearcher-ag - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - - run: go generate openapi3/refsgenerator.go + - run: go generate ./... - run: git --no-pager diff --exit-code - run: ./maps.sh @@ -71,11 +61,11 @@ jobs: - run: go test ./... - if: runner.os == 'Linux' - run: go test -count=10 ./... + run: go test -count=10 -short ./... env: GOARCH: '386' - - run: go test -count=10 ./... - - run: go test -count=2 -covermode=atomic ./... + - run: go test -count=10 -short ./... + - run: go test -count=2 -short -covermode=atomic ./... - run: go test -v -count=10 -run TestRaceyPatternSchemaValidateHindersIt -race ./... env: CGO_ENABLED: '1' @@ -109,12 +99,12 @@ jobs: - if: runner.os == 'Linux' name: Check for superfluous trailing whitespace run: | - ! grep -IErn '\s$' --exclude-dir={.git,target,pgdata} + ! git grep -IErn '\s$' - if: runner.os == 'Linux' name: Ensure use of unmarshal run: | - [[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | grep -v origin.go | wc -l)" = 1 ]] + [[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | grep -v origin.go | wc -l)" = 2 ]] - if: runner.os == 'Linux' name: Use `loader := NewLoader(); loader.Load ...` @@ -138,7 +128,7 @@ jobs: - if: runner.os == 'Linux' name: Missing specification object link to definition run: | - [[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]] + [[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[0-9.]+md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]] - if: runner.os == 'Linux' name: Missing validation of unknown fields in extensions @@ -203,11 +193,11 @@ jobs: check-goimports: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v3 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: - go-version: '>=1.17.0' - - run: go install github.com/incu6us/goimports-reviser/v2@latest + go-version: '1.26' + - run: go install github.com/incu6us/goimports-reviser/v3@latest - run: which goimports-reviser - - run: find . -type f -iname '*.go' ! -iname '*.pb.go' -exec goimports-reviser -file-path {} \; + - run: find . -type f -iname '*.go' ! -iname '*.pb.go' -exec goimports-reviser {} \; - run: git --no-pager diff --exit-code diff --git a/.gitignore b/.gitignore index 9c3b22a33..597c3770e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ .idea .vscode .claude/ + +# Test fixtures +/openapi2/testdata/APIs-guru-openapi-directory-*/ +/openapi3/testdata/APIs-guru-openapi-directory-*/ diff --git a/README.md b/README.md index 9c25c70f5..55314446f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Be sure to check [OpenAPI Initiative](https://github.com/OAI)'s [great tooling l # Some recipes ## Validating an OpenAPI document ```shell -go run github.com/getkin/kin-openapi/cmd/validate@latest [--circular] [--defaults] [--examples] [--ext] [--patterns] -- +go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] -- ``` ## Loading OpenAPI document @@ -313,6 +313,15 @@ for _, path := range doc.Paths.InMatchingOrder() { ## CHANGELOG: Sub-v1 breaking API changes +### v0.137.0 +* Reinstated `openapi3.*Ptr(..)` funcs for Go 1.25 + +### v0.136.0 +* `openapi3.Schema.ExclusiveMin` and `openapi3.Schema.ExclusiveMax` fields changed from `bool` to `ExclusiveBound` (a union type holding `*bool` for OpenAPI 3.0 or `*float64` for OpenAPI 3.1). +* `openapi3.Schema.PrefixItems` field changed from `[]*SchemaRef` to `SchemaRefs`. +* `openapi3.Schema.UnevaluatedItems` and `openapi3.Schema.UnevaluatedProperties` fields changed from `*SchemaRef` to `BoolSchema` (a union type accepting either a boolean or a schema object). +* Removed `openapi3.*Ptr(..)` funcs: they all can be replaced with `new(..)` since Go 1.26 + ### v0.135.0 * `openapi3.MediaType.Encoding` field type changed from `map[string]*Encoding` to `Encodings` * `openapi3.Server.Variables` field type changed from `map[string]*ServerVariable` to `ServerVariables` diff --git a/cmd/validate/main.go b/cmd/validate/main.go index 751940708..b742a9611 100644 --- a/cmd/validate/main.go +++ b/cmd/validate/main.go @@ -32,11 +32,16 @@ var ( patterns = flag.Bool("patterns", defaultPatterns, "when false, allows schema patterns unsupported by the Go regexp engine") ) +var ( + defaultMulti = false + multi = flag.Bool("multi", defaultMulti, "when true, aggregate independent validation errors instead of returning the first one") +) + func main() { flag.Parse() filename := flag.Arg(0) if len(flag.Args()) != 1 || filename == "" { - log.Fatalf("Usage: go run github.com/getkin/kin-openapi/cmd/validate@latest [--circular] [--defaults] [--examples] [--ext] [--patterns] -- \nGot: %+v\n", os.Args) + log.Fatalf("Usage: go run github.com/getkin/kin-openapi/cmd/validate@latest [--defaults] [--examples] [--ext] [--patterns] [--multi] -- \nGot: %+v\n", os.Args) } data, err := os.ReadFile(filename) @@ -48,7 +53,7 @@ func main() { OpenAPI string `json:"openapi" yaml:"openapi"` Swagger string `json:"swagger" yaml:"swagger"` } - if err := yaml.Unmarshal(data, &vd); err != nil { + if _, err := yaml.Unmarshal(data, &vd, yaml.DecodeOpts{DisableTimestamps: true}); err != nil { log.Fatal(err) } @@ -77,6 +82,9 @@ func main() { if !*patterns { opts = append(opts, openapi3.DisableSchemaPatternValidation()) } + if *multi { + opts = append(opts, openapi3.EnableMultiError()) + } if err = doc.Validate(loader.Context, opts...); err != nil { log.Fatalln("Validation error:", err) @@ -96,9 +104,12 @@ func main() { if *patterns != defaultPatterns { log.Fatal("Flag --patterns is only for OpenAPIv3") } + if *multi != defaultMulti { + log.Fatal("Flag --multi is only for OpenAPIv3") + } var doc openapi2.T - if err := yaml.Unmarshal(data, &doc); err != nil { + if _, err := yaml.Unmarshal(data, &doc, yaml.DecodeOpts{DisableTimestamps: true}); err != nil { log.Fatalln("Loading error:", err) } diff --git a/go.mod b/go.mod index 035db2aca..612d6ac65 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,15 @@ module github.com/getkin/kin-openapi -go 1.22.5 +go 1.25 require ( github.com/go-openapi/jsonpointer v0.21.0 github.com/gorilla/mux v1.8.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/oasdiff/yaml v0.0.9 - github.com/oasdiff/yaml3 v0.0.9 + github.com/oasdiff/yaml v0.1.1 + github.com/oasdiff/yaml3 v0.0.14 github.com/perimeterx/marshmallow v1.1.5 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/stretchr/testify v1.9.0 github.com/woodsbury/decimal128 v1.3.0 ) @@ -20,5 +21,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 06caaea54..5a5abbe3f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= @@ -18,22 +20,26 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/oasdiff/yaml v0.0.9 h1:zQOvd2UKoozsSsAknnWoDJlSK4lC0mpmjfDsfqNwX48= -github.com/oasdiff/yaml v0.0.9/go.mod h1:8lvhgJG4xiKPj3HN5lDow4jZHPlx1i7dIwzkdAo6oAM= -github.com/oasdiff/yaml3 v0.0.9 h1:rWPrKccrdUm8J0F3sGuU+fuh9+1K/RdJlWF7O/9yw2g= -github.com/oasdiff/yaml3 v0.0.9/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY= +github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU= +github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw= +github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/maps.sh b/maps.sh index a7554cc40..80c380795 100755 --- a/maps.sh +++ b/maps.sh @@ -36,7 +36,7 @@ package openapi3 import ( "encoding/json" - "slices" + "maps" "strings" "github.com/go-openapi/jsonpointer" @@ -48,12 +48,14 @@ EOF test_header() { cat <"$maplike_test" -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestMaplikeMethods(t *testing.T) { @@ -82,8 +84,13 @@ EOF } -maplike_ValueSetLenDelete() { +maplike_KeysValueSetLenDelete() { cat <>"$maplike" +// Keys returns the ${name} keys in a fixed order +func (${name} ${type}) Keys() []string { + return componentNames(${name}.Map()) +} + // Value returns the ${name} for key or nil func (${name} ${type}) Value(key string) ${value_type} { if ${name}.Len() == 0 { @@ -123,9 +130,7 @@ func (${name} ${type}) Map() (m map[string]${value_type}) { return make(map[string]${value_type}) } m = make(map[string]${value_type}, len(${name}.m)) - for k, v := range ${name}.m { - m[k] = v - } + maps.Copy(m, ${name}.m) return } @@ -165,11 +170,9 @@ func (${name} ${type}) MarshalYAML() (any, error) { return nil, nil } m := make(map[string]any, ${name}.Len()+len(${name}.Extensions)) - for k, v := range ${name}.Extensions { - m[k] = v - } - for k, v := range ${name}.Map() { - m[k] = v + maps.Copy(m, ${name}.Extensions) + for _, k := range ${name}.Keys() { + m[k] = ${name}.m[k] } return m, nil } @@ -190,18 +193,12 @@ func (${name} ${type}) UnmarshalJSON(data []byte) (err error) { return } - ks := make([]string, 0, len(m)) - for k := range m { - ks = append(ks, k) - } - slices.Sort(ks) - x := ${type#'*'}{ Extensions: make(map[string]any), m: make(map[string]${value_type}, len(m)), } - for _, k := range ks { + for _, k := range componentNames(m) { v := m[k] if strings.HasPrefix(k, "x-") { x.Extensions[k] = v @@ -269,12 +266,12 @@ for i in "${!types[@]}"; do name=${names[$i]} type="$type" name="$name" value_type="$value_type" maplike_NewWithCapa - type="$type" name="$name" value_type="$value_type" maplike_ValueSetLenDelete + type="$type" name="$name" value_type="$value_type" maplike_KeysValueSetLenDelete type="$type" name="$name" deref_v="$deref_v" maplike_Pointable type="$type" name="$name" value_type="$value_type" maplike_UnMarsh [[ $((i+1)) != "${#types[@]}" ]] && echo >>"$maplike" - type="$type" value_type="$value_type" test_body + type="${type/'*'/*openapi3.}" value_type="${value_type/'*'/*openapi3.}" test_body done diff --git a/openapi2/issues1010_test.go b/openapi2/issues1010_test.go index 2f84dc946..3c2d6ad21 100644 --- a/openapi2/issues1010_test.go +++ b/openapi2/issues1010_test.go @@ -1,10 +1,12 @@ -package openapi2 +package openapi2_test import ( "encoding/json" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2" ) func TestIssue1010(t *testing.T) { @@ -91,7 +93,7 @@ func TestIssue1010(t *testing.T) { } `) - var doc2 T + var doc2 openapi2.T err := json.Unmarshal(v2, &doc2) require.NoError(t, err) require.Equal(t, "petType", doc2.Definitions["Pet"].Value.Discriminator) diff --git a/openapi2/marsh.go b/openapi2/marsh.go index 1745734b1..6e3bb540a 100644 --- a/openapi2/marsh.go +++ b/openapi2/marsh.go @@ -25,7 +25,7 @@ func unmarshal(data []byte, v any) error { } // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys - if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil { + if _, yamlErr = yaml.Unmarshal(data, v, yaml.DecodeOpts{DisableTimestamps: true}); yamlErr == nil { return nil } diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go index bd3375339..86eb8c9da 100644 --- a/openapi2/openapi2.go +++ b/openapi2/openapi2.go @@ -2,6 +2,7 @@ package openapi2 import ( "encoding/json" + "maps" "github.com/getkin/kin-openapi/openapi3" ) @@ -30,9 +31,7 @@ type T struct { // MarshalJSON returns the JSON encoding of T. func (doc T) MarshalJSON() ([]byte, error) { m := make(map[string]any, 15+len(doc.Extensions)) - for k, v := range doc.Extensions { - m[k] = v - } + maps.Copy(m, doc.Extensions) m["swagger"] = doc.Swagger m["info"] = doc.Info if x := doc.ExternalDocs; x != nil { diff --git a/openapi2/openapi2_test.go b/openapi2/openapi2_test.go index d1b3aa56a..c385215d1 100644 --- a/openapi2/openapi2_test.go +++ b/openapi2/openapi2_test.go @@ -42,7 +42,7 @@ func Example() { panic(err) } var docAgainFromYAML openapi2.T - if err = yaml.Unmarshal(outputYAML, &docAgainFromYAML); err != nil { + if _, err = yaml.Unmarshal(outputYAML, &docAgainFromYAML, yaml.DecodeOpts{DisableTimestamps: true}); err != nil { panic(err) } if !reflect.DeepEqual(doc, docAgainFromYAML) { diff --git a/openapi2/operation.go b/openapi2/operation.go index 64f10d1f1..be73f9423 100644 --- a/openapi2/operation.go +++ b/openapi2/operation.go @@ -2,6 +2,7 @@ package openapi2 import ( "encoding/json" + "maps" "github.com/getkin/kin-openapi/openapi3" ) @@ -26,9 +27,7 @@ type Operation struct { // MarshalJSON returns the JSON encoding of Operation. func (operation Operation) MarshalJSON() ([]byte, error) { m := make(map[string]any, 12+len(operation.Extensions)) - for k, v := range operation.Extensions { - m[k] = v - } + maps.Copy(m, operation.Extensions) if x := operation.Summary; x != "" { m["summary"] = x } diff --git a/openapi2/parameter.go b/openapi2/parameter.go index 99dc92bc9..796cb204a 100644 --- a/openapi2/parameter.go +++ b/openapi2/parameter.go @@ -2,6 +2,7 @@ package openapi2 import ( "encoding/json" + "maps" "github.com/getkin/kin-openapi/openapi3" ) @@ -45,9 +46,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) { } m := make(map[string]any, 24+len(parameter.Extensions)) - for k, v := range parameter.Extensions { - m[k] = v - } + maps.Copy(m, parameter.Extensions) if x := parameter.In; x != "" { m["in"] = x diff --git a/openapi2/path_item.go b/openapi2/path_item.go index 624cc74dc..446e444cd 100644 --- a/openapi2/path_item.go +++ b/openapi2/path_item.go @@ -3,6 +3,7 @@ package openapi2 import ( "encoding/json" "fmt" + "maps" "net/http" "github.com/getkin/kin-openapi/openapi3" @@ -30,9 +31,7 @@ func (pathItem PathItem) MarshalJSON() ([]byte, error) { } m := make(map[string]any, 8+len(pathItem.Extensions)) - for k, v := range pathItem.Extensions { - m[k] = v - } + maps.Copy(m, pathItem.Extensions) if x := pathItem.Delete; x != nil { m["delete"] = x } diff --git a/openapi2/ref.go b/openapi2/ref.go index e591d143e..5dbbd08c0 100644 --- a/openapi2/ref.go +++ b/openapi2/ref.go @@ -1,7 +1,5 @@ package openapi2 -//go:generate go run refsgenerator.go - // Ref is specified by OpenAPI/Swagger 2.0 standard. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object type Ref struct { diff --git a/openapi2/refs.go b/openapi2/refs.go index 36f92a19d..6690ee5d1 100644 --- a/openapi2/refs.go +++ b/openapi2/refs.go @@ -26,8 +26,6 @@ type SchemaRef struct { var _ jsonpointer.JSONPointable = (*SchemaRef)(nil) -func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } - // RefString returns the $ref value. func (x *SchemaRef) RefString() string { return x.Ref } @@ -37,16 +35,6 @@ func (x *SchemaRef) CollectionName() string { return "schemas" } // RefPath returns the path of the $ref relative to the root document. func (x *SchemaRef) RefPath() *url.URL { return copyURI(x.refPath) } -func (x *SchemaRef) setRefPath(u *url.URL) { - // Once the refPath is set don't override. References can be loaded - // multiple times not all with access to the correct path info. - if x.refPath != nil { - return - } - - x.refPath = copyURI(u) -} - // MarshalYAML returns the YAML encoding of SchemaRef. func (x SchemaRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { diff --git a/openapi2/response.go b/openapi2/response.go index 3a2983ce1..0bad9ee18 100644 --- a/openapi2/response.go +++ b/openapi2/response.go @@ -2,6 +2,7 @@ package openapi2 import ( "encoding/json" + "maps" "github.com/getkin/kin-openapi/openapi3" ) @@ -24,9 +25,7 @@ func (response Response) MarshalJSON() ([]byte, error) { } m := make(map[string]any, 4+len(response.Extensions)) - for k, v := range response.Extensions { - m[k] = v - } + maps.Copy(m, response.Extensions) if x := response.Description; x != "" { m["description"] = x } diff --git a/openapi2/schema.go b/openapi2/schema.go index 64bed2751..c787113f3 100644 --- a/openapi2/schema.go +++ b/openapi2/schema.go @@ -2,15 +2,13 @@ package openapi2 import ( "encoding/json" - "strings" + "maps" "github.com/getkin/kin-openapi/openapi3" ) -type ( - Schemas map[string]*SchemaRef - SchemaRefs []*SchemaRef -) +type Schemas map[string]*SchemaRef +type SchemaRefs []*SchemaRef // Schema is specified by OpenAPI/Swagger 2.0 standard. // See https://swagger.io/specification/v2/#schema-object @@ -77,9 +75,7 @@ func (schema Schema) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Schema. func (schema Schema) MarshalYAML() (any, error) { m := make(map[string]any, 36+len(schema.Extensions)) - for k, v := range schema.Extensions { - m[k] = v - } + maps.Copy(m, schema.Extensions) if x := schema.AllOf; len(x) != 0 { m["allOf"] = x @@ -258,12 +254,5 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { } *schema = Schema(x) - - if schema.Format == "date" { - // This is a fix for: https://github.com/getkin/kin-openapi/issues/697 - if eg, ok := schema.Example.(string); ok { - schema.Example = strings.TrimSuffix(eg, "T00:00:00Z") - } - } return nil } diff --git a/openapi2/security_scheme.go b/openapi2/security_scheme.go index cd6e6a5dd..b4674ce2d 100644 --- a/openapi2/security_scheme.go +++ b/openapi2/security_scheme.go @@ -2,6 +2,7 @@ package openapi2 import ( "encoding/json" + "maps" "github.com/getkin/kin-openapi/openapi3" ) @@ -31,9 +32,7 @@ func (securityScheme SecurityScheme) MarshalJSON() ([]byte, error) { } m := make(map[string]any, 10+len(securityScheme.Extensions)) - for k, v := range securityScheme.Extensions { - m[k] = v - } + maps.Copy(m, securityScheme.Extensions) if x := securityScheme.Description; x != "" { m["description"] = x } diff --git a/openapi2/testdata/apis_guru_openapi_directory/powerdns_local_0_0_13_swagger_yaml__load b/openapi2/testdata/apis_guru_openapi_directory/powerdns_local_0_0_13_swagger_yaml__load new file mode 100644 index 000000000..551a41634 --- /dev/null +++ b/openapi2/testdata/apis_guru_openapi_directory/powerdns_local_0_0_13_swagger_yaml__load @@ -0,0 +1 @@ +error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into field Schema.items of type openapi2.Schema diff --git a/openapi2/v2_apis_guru_openapi_directory_test.go b/openapi2/v2_apis_guru_openapi_directory_test.go new file mode 100644 index 000000000..4622f06c4 --- /dev/null +++ b/openapi2/v2_apis_guru_openapi_directory_test.go @@ -0,0 +1,195 @@ +package openapi2_test + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "errors" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/oasdiff/yaml" + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2" +) + +var goldens = filepath.Join("testdata", "apis_guru_openapi_directory") + +func newUnderscorer() *strings.Replacer { + var chars []string + for c := range strings.SplitSeq("\\/_-( ).~", "") { + chars = append(chars, c, "_") + } + return strings.NewReplacer(chars...) +} + +func isOpenAPIVersion(t *testing.T, path, str string) bool { + file, err := os.Open(path) + require.NoError(t, err) + defer file.Close() + + r := bufio.NewScanner(file) + for r.Scan() { + if strings.Contains(r.Text(), str) { + return true + } + } + return false +} + +func shortNameFromPath(path string) string { + shortName := filepath.Base(path) + shortName = strings.TrimSuffix(shortName, "__load") + shortName = strings.TrimSuffix(shortName, "__validate") + return shortName +} + +func golden(t *testing.T, e error, shortName, task string) { + errf := filepath.Join(goldens, shortName+"__"+task) + + if e == nil { + _ = os.Remove(errf) + return + } + + expected, _ := os.ReadFile(errf) + expectedStr := strings.ReplaceAll(string(expected), "\r\n", "\n") + got := strings.ReplaceAll(e.Error(), "\r\n", "\n") + if !strings.HasSuffix(got, "\n") { + got += "\n" + } + + if expectedStr != got { + err := os.WriteFile(errf, []byte(got), 0644) + require.NoError(t, err) + + require.Equal(t, expectedStr, got) + } +} + +func TestV2ApisGuruOpenapiDirectory(t *testing.T) { + if testing.Short() { + t.Skip("skipping APIs Guru's large sets of documents") + } + + commit := os.Getenv("APISGURU_COMMIT") + if commit == "" { + commit = "f7207cf0a5c56081d275ebae4cf615249323385d" // On 2026-04-19 + } + dirName := "APIs-guru-openapi-directory-" + commit[0:7] + targetDir := filepath.Join("testdata", dirName) + + if _, err := os.Stat(targetDir); errors.Is(err, os.ErrNotExist) { + req, err := http.NewRequestWithContext(t.Context(), "GET", "https://github.com/APIs-guru/openapi-directory/tarball/"+commit, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + gzr, err := gzip.NewReader(resp.Body) + require.NoError(t, err) + defer gzr.Close() + tr := tar.NewReader(gzr) + + for { + header, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + target := filepath.Join(targetDir, header.Name) + switch header.Typeflag { + case tar.TypeDir: + err := os.MkdirAll(target, 0755) + require.NoError(t, err) + case tar.TypeReg: + err := os.MkdirAll(filepath.Dir(target), 0755) + require.NoError(t, err) + + func() { + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + require.NoError(t, err) + defer f.Close() + + _, err = io.Copy(f, tr) + require.NoError(t, err) + }() + } + } + } + + root := filepath.Join(targetDir, dirName, "APIs") + + underscorer := newUnderscorer() + checked := make(map[string]struct{}) + + var paths []string + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + paths = append(paths, path) + } + return nil + }) + require.NoError(t, err) + t.Logf("found %v files in %q", len(paths), root) + + for _, path := range paths { + shortName := underscorer.Replace(strings.TrimPrefix(path, root)[1:]) + + if isOpenAPIVersion(t, path, "swagger: ") { + t.Run(shortName, func(t *testing.T) { + if disabled(shortName) { + t.SkipNow() + return + } + checked[shortName] = struct{}{} + t.Parallel() + + data, err := os.ReadFile(path) + require.NoError(t, err) + + var doc openapi2.T + _, err = yaml.Unmarshal(data, &doc, yaml.DecodeOpts{DisableTimestamps: true}) + golden(t, err, shortName, "load") + }) + } + } + + err = filepath.Walk(goldens, func(path string, info os.FileInfo, err error) error { + require.NotNil(t, info) + if !info.IsDir() { + shortName := shortNameFromPath(path) + delete(checked, shortName) + } + return nil + }) + require.NoError(t, err) + + files, err := filepath.Glob(goldens + "*") + require.NoError(t, err) + for _, file := range files { + shortName := shortNameFromPath(file) + if _, ok := checked[shortName]; ok || disabled(shortName) { + err := os.Remove(file) + require.NoError(t, err) + } + } + +} + +func disabled(shortName string) bool { + switch shortName { + case "vvv keep these", + "quarantine_country_1_0_swagger_yaml", + "^^^ lines sorted": + return true + } + return false +} diff --git a/openapi2conv/issue1008_test.go b/openapi2conv/issue1008_test.go index 4d8dfe5ef..fc7f544a9 100644 --- a/openapi2conv/issue1008_test.go +++ b/openapi2conv/issue1008_test.go @@ -1,7 +1,6 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -43,7 +42,7 @@ paths: v3, err := v2v3YAML(v2) require.NoError(t, err) - err = v3.Validate(context.Background()) + err = v3.Validate(t.Context()) require.NoError(t, err) assert.Equal(t, []string{"alpaca", "bee", "zebra"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content.Get("multipart/form-data").Schema.Value.Required) } diff --git a/openapi2conv/issue1016_test.go b/openapi2conv/issue1016_test.go index acd87d077..17d89a006 100644 --- a/openapi2conv/issue1016_test.go +++ b/openapi2conv/issue1016_test.go @@ -1,12 +1,12 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "testing" - "github.com/getkin/kin-openapi/openapi2" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2" ) func TestIssue1016(t *testing.T) { @@ -75,7 +75,7 @@ func TestIssue1016(t *testing.T) { doc3, err := v2v3YAML(v2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) require.Equal(t, "#/components/schemas/Pet", doc3.Components.Schemas["PetDirectory"].Value.AdditionalProperties.Schema.Ref) } diff --git a/openapi2conv/issue1049_test.go b/openapi2conv/issue1049_test.go index 04ddf8581..dfc3c8c06 100644 --- a/openapi2conv/issue1049_test.go +++ b/openapi2conv/issue1049_test.go @@ -1,7 +1,6 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "testing" @@ -81,6 +80,6 @@ func TestIssue1049(t *testing.T) { const expected = `{"components":{"schemas":{"BaseDictResp":{"description":"BaseDictResp description","properties":{"key":{"description":"key of map","type":"string"},"value":{"description":"value of map","type":"string"}},"title":"BaseDictResp","type":"object"},"ResponseOfMap":{"properties":{"data":{"additionalProperties":{"items":{"$ref":"#/components/schemas/BaseDictResp"},"type":"array"},"description":"response data","type":"object"}},"title":"ResponseOfMap","type":"object"}}},"info":{"description":"Test for additionalProperties","title":"API Test","version":"v1"},"openapi":"3.0.3","paths":{"/map":{"post":{"description":"api test description","operationId":"apiTestUsingPOST","responses":{"200":{"content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseOfMap"}}},"description":"OK"}},"summary":"api test summary"}}},"servers":[{"url":"https://Test/"}]}` require.JSONEq(t, expected, string(spec3)) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) } diff --git a/openapi2conv/issue1062_test.go b/openapi2conv/issue1062_test.go new file mode 100644 index 000000000..1c308774b --- /dev/null +++ b/openapi2conv/issue1062_test.go @@ -0,0 +1,83 @@ +package openapi2conv_test + +import ( + "testing" + + "github.com/oasdiff/yaml" + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2conv" + "github.com/getkin/kin-openapi/openapi3" +) + +// Regression test for #1062. +// +// FromV3RequestBodyFormData iterates the properties of a form-data schema +// and, for any property typed as an array, recurses into the items via +// FromV3SchemaRef(val.Items, nil) — passing the components table as nil. +// With the old code, an items entry that was a $ref into +// #/components/schemas/... nil-dereferenced on `components.Schemas[name]` +// before the lookup could even happen. +// +// The minified spec below reproduces the OpenAI OpenAPI 3 shape that +// triggered the original report: a multipart/form-data request body whose +// `documents` field is an array of $ref-ed component schemas. This must +// convert cleanly without a panic. +func TestIssue1062_FormDataArrayOfRefDoesNotPanic(t *testing.T) { + const v3Spec = ` +openapi: 3.0.3 +info: + title: issue 1062 minified + version: 1.0.0 +paths: + /v1/upload: + post: + operationId: uploadDocuments + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + documents: + type: array + items: + $ref: '#/components/schemas/Document' + responses: + '200': + description: ok +components: + schemas: + Document: + type: object + properties: + id: + type: string + name: + type: string +` + + var doc3 openapi3.T + _, err := yaml.Unmarshal([]byte(v3Spec), &doc3, yaml.DecodeOpts{DisableTimestamps: true}) + require.NoError(t, err, "unmarshal v3 spec") + + // Pre-fix: this call panicked with + // "runtime error: invalid memory reference or nil pointer dereference" + // inside FromV3SchemaRef when it deref'd nil components.Schemas. + doc2, err := openapi2conv.FromV3(&doc3) + require.NoError(t, err, "FromV3 must not error on form-data array of $refs") + require.NotNil(t, doc2) + + // Sanity: the operation made it through to v2 with a formData parameter. + op := doc2.Paths["/v1/upload"].Post + require.NotNil(t, op, "POST /v1/upload should be present after conversion") + var sawDocuments bool + for _, p := range op.Parameters { + if p.In == "formData" && p.Name == "documents" { + sawDocuments = true + break + } + } + require.True(t, sawDocuments, "expected a formData parameter named 'documents'") +} diff --git a/openapi2conv/issue1069_test.go b/openapi2conv/issue1069_test.go index f7bb8bcdc..8a1d385bd 100644 --- a/openapi2conv/issue1069_test.go +++ b/openapi2conv/issue1069_test.go @@ -1,14 +1,15 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "testing" - "github.com/getkin/kin-openapi/openapi2" - "github.com/getkin/kin-openapi/openapi3" "github.com/oasdiff/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue1069V2ToV3(t *testing.T) { @@ -99,7 +100,7 @@ paths: t.Run(tt.name, func(t *testing.T) { v3, err := v2v3YAML([]byte(tt.v2Spec)) require.NoError(t, err) - err = v3.Validate(context.Background()) + err = v3.Validate(t.Context()) require.NoError(t, err) tt.validate(t, v3) }) @@ -198,10 +199,10 @@ paths: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var doc3 openapi3.T - err := yaml.Unmarshal([]byte(tt.v3Spec), &doc3) + _, err := yaml.Unmarshal([]byte(tt.v3Spec), &doc3, yaml.DecodeOpts{DisableTimestamps: true}) require.NoError(t, err) - v2, err := FromV3(&doc3) + v2, err := openapi2conv.FromV3(&doc3) require.NoError(t, err) tt.validate(t, v2) }) diff --git a/openapi2conv/issue1091_test.go b/openapi2conv/issue1091_test.go index 70e1bef41..31a007920 100644 --- a/openapi2conv/issue1091_test.go +++ b/openapi2conv/issue1091_test.go @@ -1,4 +1,4 @@ -package openapi2conv +package openapi2conv_test import ( "encoding/json" @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" ) func TestIssue1091_PropertyExtensions(t *testing.T) { @@ -44,7 +45,7 @@ func TestIssue1091_PropertyExtensions(t *testing.T) { } // Convert to v3 - v3SchemaRef := ToV3SchemaRef(v2SchemaRef) + v3SchemaRef := openapi2conv.ToV3SchemaRef(v2SchemaRef) // Verify that the conversion was successful require.NotNil(t, v3SchemaRef) @@ -96,7 +97,7 @@ func TestIssue1091_SchemaLevelExtensions(t *testing.T) { } // Convert to v3 - v3SchemaRef := ToV3SchemaRef(v2SchemaRef) + v3SchemaRef := openapi2conv.ToV3SchemaRef(v2SchemaRef) // Verify that the conversion was successful require.NotNil(t, v3SchemaRef) @@ -168,7 +169,7 @@ func TestIssue1091_CompleteV2ToV3Conversion(t *testing.T) { require.NoError(t, err) // Convert to v3 - v3Doc, err := ToV3(&v2Doc) + v3Doc, err := openapi2conv.ToV3(&v2Doc) require.NoError(t, err) // Verify that User schema has extensions preserved diff --git a/openapi2conv/issue187_test.go b/openapi2conv/issue187_test.go index 4cec87532..3978d3693 100644 --- a/openapi2conv/issue187_test.go +++ b/openapi2conv/issue187_test.go @@ -1,7 +1,6 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "testing" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" ) @@ -17,16 +17,16 @@ func v2v3JSON(spec2 []byte) (doc3 *openapi3.T, err error) { if err = json.Unmarshal(spec2, &doc2); err != nil { return } - doc3, err = ToV3(&doc2) + doc3, err = openapi2conv.ToV3(&doc2) return } func v2v3YAML(spec2 []byte) (doc3 *openapi3.T, err error) { var doc2 openapi2.T - if err = yaml.Unmarshal(spec2, &doc2); err != nil { + if _, err = yaml.Unmarshal(spec2, &doc2, yaml.DecodeOpts{DisableTimestamps: true}); err != nil { return } - doc3, err = ToV3(&doc2) + doc3, err = openapi2conv.ToV3(&doc2) return } @@ -106,7 +106,7 @@ func TestIssue187(t *testing.T) { const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.3","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}` require.JSONEq(t, expected, string(spec3)) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) } @@ -165,7 +165,7 @@ paths: ` require.YAMLEq(t, expected, string(spec3)) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) } @@ -188,7 +188,7 @@ securityDefinitions: _, err = yaml.Marshal(doc3) require.NoError(t, err) - doc2, err := FromV3(doc3) + doc2, err := openapi2conv.FromV3(doc3) require.NoError(t, err) require.Equal(t, "application", doc2.SecurityDefinitions["OAuth2Application"].Flow) } diff --git a/openapi2conv/issue440_test.go b/openapi2conv/issue440_test.go index 5c9f67dc0..86d04cc87 100644 --- a/openapi2conv/issue440_test.go +++ b/openapi2conv/issue440_test.go @@ -1,7 +1,6 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "os" "testing" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" ) @@ -20,9 +20,9 @@ func TestIssue440(t *testing.T) { err = json.NewDecoder(doc2file).Decode(&doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) require.Equal(t, openapi3.Servers{ {URL: "https://petstore.swagger.io/v2"}, @@ -32,9 +32,9 @@ func TestIssue440(t *testing.T) { doc2.Host = "your-bot-domain.de" doc2.Schemes = nil doc2.BasePath = "" - doc3, err = ToV3(&doc2) + doc3, err = openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) require.Equal(t, openapi3.Servers{ {URL: "https://your-bot-domain.de/"}, @@ -43,7 +43,8 @@ func TestIssue440(t *testing.T) { doc2.Host = "https://your-bot-domain.de" doc2.Schemes = nil doc2.BasePath = "" - doc3, err = ToV3(&doc2) + doc3, err = openapi2conv.ToV3(&doc2) require.Error(t, err) require.ErrorContains(t, err, `invalid host`) + require.Nil(t, doc3) } diff --git a/openapi2conv/issue558_test.go b/openapi2conv/issue558_test.go index b7fb9dc59..79668c22f 100644 --- a/openapi2conv/issue558_test.go +++ b/openapi2conv/issue558_test.go @@ -1,10 +1,12 @@ -package openapi2conv +package openapi2conv_test import ( "testing" "github.com/oasdiff/yaml" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi2conv" ) func TestPR558(t *testing.T) { @@ -31,7 +33,7 @@ paths: _, err = yaml.Marshal(doc3) require.NoError(t, err) - doc2, err := FromV3(doc3) + doc2, err := openapi2conv.FromV3(doc3) require.NoError(t, err) require.NotEmpty(t, doc2.Paths["/test"].Get.Deprecated) } diff --git a/openapi2conv/issue573_test.go b/openapi2conv/issue573_test.go index 0f9a35fb6..d57e7ee4c 100644 --- a/openapi2conv/issue573_test.go +++ b/openapi2conv/issue573_test.go @@ -1,4 +1,4 @@ -package openapi2conv +package openapi2conv_test import ( "testing" diff --git a/openapi2conv/issue847_test.go b/openapi2conv/issue847_test.go index 79dbb21ee..deb035e86 100644 --- a/openapi2conv/issue847_test.go +++ b/openapi2conv/issue847_test.go @@ -1,7 +1,6 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -32,7 +31,7 @@ paths: v3, err := v2v3YAML(v2) require.NoError(t, err) - err = v3.Validate(context.Background()) + err = v3.Validate(t.Context()) require.NoError(t, err) require.Equal(t, []string{"file"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Required) diff --git a/openapi2conv/issue979_test.go b/openapi2conv/issue979_test.go index 8d87902c9..c070a029f 100644 --- a/openapi2conv/issue979_test.go +++ b/openapi2conv/issue979_test.go @@ -1,13 +1,13 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "testing" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" ) @@ -55,9 +55,9 @@ func TestIssue979(t *testing.T) { err := json.Unmarshal(v2, &doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) require.Equal(t, &openapi3.Types{"string"}, doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value.Type) diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go index 223767329..058ac57e5 100644 --- a/openapi2conv/openapi2_conv.go +++ b/openapi2conv/openapi2_conv.go @@ -4,6 +4,7 @@ import ( "cmp" "errors" "fmt" + "maps" "net/url" "slices" "strings" @@ -123,7 +124,7 @@ func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *o Extensions: stripNonExtensions(pathItem.Extensions), } for method, operation := range pathItem.Operations() { - doc3Operation, err := ToV3Operation(doc2, components, pathItem, operation, consumes) + doc3Operation, err := ToV3Operation(components, pathItem, operation, consumes) if err != nil { return nil, err } @@ -145,7 +146,7 @@ func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *o return doc3, nil } -func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) { +func ToV3Operation(components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) { if operation == nil { return nil, nil } @@ -269,8 +270,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete Enum: parameter.Enum, Min: parameter.Minimum, Max: parameter.Maximum, - ExclusiveMin: parameter.ExclusiveMin, - ExclusiveMax: parameter.ExclusiveMax, + ExclusiveMin: openapi3.ExclusiveBound{Bool: boolPtr(parameter.ExclusiveMin)}, + ExclusiveMax: openapi3.ExclusiveBound{Bool: boolPtr(parameter.ExclusiveMax)}, MinLength: parameter.MinLength, MaxLength: parameter.MaxLength, Default: parameter.Default, @@ -495,8 +496,8 @@ func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef { Example: schema.Value.Example, ExternalDocs: schema.Value.ExternalDocs, UniqueItems: schema.Value.UniqueItems, - ExclusiveMin: schema.Value.ExclusiveMin, - ExclusiveMax: schema.Value.ExclusiveMax, + ExclusiveMin: openapi3.ExclusiveBound{Bool: boolPtr(schema.Value.ExclusiveMin)}, + ExclusiveMax: openapi3.ExclusiveBound{Bool: boolPtr(schema.Value.ExclusiveMax)}, ReadOnly: schema.Value.ReadOnly, WriteOnly: schema.Value.WriteOnly, AllowEmptyValue: schema.Value.AllowEmptyValue, @@ -639,9 +640,7 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu flows := &openapi3.OAuthFlows{} result.Flows = flows scopesMap := make(map[string]string) - for scope, desc := range securityScheme.Scopes { - scopesMap[scope] = desc - } + maps.Copy(scopesMap, securityScheme.Scopes) flow := &openapi3.OAuthFlow{ AuthorizationURL: securityScheme.AuthorizationURL, TokenURL: securityScheme.TokenURL, @@ -767,7 +766,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) { if m := doc3.Components.SecuritySchemes; m != nil { doc2SecuritySchemes := make(map[string]*openapi2.SecurityScheme) for id, securityScheme := range m { - v, err := FromV3SecurityScheme(doc3, securityScheme) + v, err := FromV3SecurityScheme(securityScheme) if err != nil { return nil, err } @@ -847,11 +846,21 @@ func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3. func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) { if ref := schema.Ref; ref != "" { + // FromV3RequestBodyFormData (and other recursive call sites in + // this file) pass components=nil when recursing into array + // items and nested refs. Without guarding, components.Schemas + // nil-derefs before we even have a chance to look up the + // component, so a ref like '#/components/schemas/CreateEmbeddingRequest' + // inside an array schema crashes the converter (#1062). + // Treat a missing components table the same as 'the target + // schema is not known locally': emit a plain $ref and move on. name := getParameterNameFromNewRef(ref) - if val, ok := components.Schemas[name]; ok { - if val.Value.Format == "binary" { - v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1) - return nil, &openapi2.Parameter{Ref: v2Ref} + if components != nil { + if val, ok := components.Schemas[name]; ok { + if val.Value.Format == "binary" { + v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1) + return nil, &openapi2.Parameter{Ref: v2Ref} + } } } @@ -866,26 +875,20 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components if schema.Value != nil { if schema.Value.Type.Is("string") && schema.Value.Format == "binary" { paramType := &openapi3.Types{"file"} - required := false - value, _ := schema.Value.Extensions["x-formData-name"] + value := schema.Value.Extensions["x-formData-name"] originalName, _ := value.(string) - for _, prop := range schema.Value.Required { - if originalName == prop { - required = true - break - } - } + required := slices.Contains(schema.Value.Required, originalName) return nil, &openapi2.Parameter{ In: "formData", Name: originalName, Description: schema.Value.Description, Type: paramType, Enum: schema.Value.Enum, - Minimum: schema.Value.Min, - Maximum: schema.Value.Max, - ExclusiveMin: schema.Value.ExclusiveMin, - ExclusiveMax: schema.Value.ExclusiveMax, + Minimum: effectiveMin(schema.Value.Min, schema.Value.ExclusiveMin), + Maximum: effectiveMax(schema.Value.Max, schema.Value.ExclusiveMax), + ExclusiveMin: exclusiveBoundToBool(schema.Value.ExclusiveMin), + ExclusiveMax: exclusiveBoundToBool(schema.Value.ExclusiveMax), MinLength: schema.Value.MinLength, MaxLength: schema.Value.MaxLength, Default: schema.Value.Default, @@ -912,15 +915,15 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components Example: schema.Value.Example, ExternalDocs: schema.Value.ExternalDocs, UniqueItems: schema.Value.UniqueItems, - ExclusiveMin: schema.Value.ExclusiveMin, - ExclusiveMax: schema.Value.ExclusiveMax, + ExclusiveMin: exclusiveBoundToBool(schema.Value.ExclusiveMin), + ExclusiveMax: exclusiveBoundToBool(schema.Value.ExclusiveMax), ReadOnly: schema.Value.ReadOnly, WriteOnly: schema.Value.WriteOnly, AllowEmptyValue: schema.Value.AllowEmptyValue, Deprecated: schema.Value.Deprecated, XML: schema.Value.XML, - Min: schema.Value.Min, - Max: schema.Value.Max, + Min: effectiveMin(schema.Value.Min, schema.Value.ExclusiveMin), + Max: effectiveMax(schema.Value.Max, schema.Value.ExclusiveMax), MultipleOf: schema.Value.MultipleOf, MinLength: schema.Value.MinLength, MaxLength: schema.Value.MaxLength, @@ -1027,13 +1030,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter if val.Format == "binary" { typ = &openapi3.Types{"file"} } - required := false - for _, name := range val.Required { - if name == propName { - required = true - break - } - } + required := slices.Contains(val.Required, propName) var v2Items *openapi2.SchemaRef if val.Items != nil { @@ -1046,16 +1043,16 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter In: "formData", Extensions: stripNonExtensions(val.Extensions), Enum: val.Enum, - ExclusiveMin: val.ExclusiveMin, - ExclusiveMax: val.ExclusiveMax, + ExclusiveMin: exclusiveBoundToBool(val.ExclusiveMin), + ExclusiveMax: exclusiveBoundToBool(val.ExclusiveMax), MinLength: val.MinLength, MaxLength: val.MaxLength, Default: val.Default, Items: v2Items, MinItems: val.MinItems, MaxItems: val.MaxItems, - Maximum: val.Max, - Minimum: val.Min, + Maximum: effectiveMax(val.Max, val.ExclusiveMax), + Minimum: effectiveMin(val.Min, val.ExclusiveMin), Pattern: val.Pattern, // CollectionFormat: val.CollectionFormat, // Format: val.Format, @@ -1249,7 +1246,7 @@ func FromV3Headers(defs openapi3.Headers, components *openapi3.Components) (map[ return headers, nil } -func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) { +func FromV3SecurityScheme(ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) { securityScheme := ref.Value if securityScheme == nil { return nil, nil @@ -1306,9 +1303,7 @@ func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*o } result.Scopes = make(map[string]string, len(flow.Scopes)) - for scope, desc := range flow.Scopes { - result.Scopes[scope] = desc - } + maps.Copy(result.Scopes, flow.Scopes) } default: return nil, fmt.Errorf("unsupported security scheme type %q", securityScheme.Type) @@ -1352,3 +1347,39 @@ func compareParameters(a, b *openapi2.Parameter) int { } return cmp.Compare(a.Ref, b.Ref) } + +// boolPtr returns a pointer to a bool, or nil if the value is false (to avoid storing empty values) +func boolPtr(b bool) *bool { + if !b { + return nil + } + return &b +} + +// exclusiveBoundToBool converts an ExclusiveBound to a bool for OpenAPI 2.0 compatibility +// In OpenAPI 2.0, exclusiveMinimum/exclusiveMaximum are boolean modifiers +func exclusiveBoundToBool(eb openapi3.ExclusiveBound) bool { + if eb.Bool != nil { + return *eb.Bool + } + // If it's a number (OpenAPI 3.1 style), we return true to indicate exclusivity + return eb.Value != nil +} + +// effectiveMin returns the minimum value for OAS 2.0 conversion, considering ExclusiveBound. +// In OAS 3.1, exclusiveMinimum is a number. In OAS 2.0, it must be in the minimum field. +func effectiveMin(min *float64, eb openapi3.ExclusiveBound) *float64 { + if min != nil { + return min + } + // If OAS 3.1 style numeric exclusive bound with no minimum, use the bound value as minimum + return eb.Value +} + +// effectiveMax returns the maximum value for OAS 2.0 conversion, considering ExclusiveBound. +func effectiveMax(max *float64, eb openapi3.ExclusiveBound) *float64 { + if max != nil { + return max + } + return eb.Value +} diff --git a/openapi2conv/openapi2_conv_test.go b/openapi2conv/openapi2_conv_test.go index ce3423787..14c400b7b 100644 --- a/openapi2conv/openapi2_conv_test.go +++ b/openapi2conv/openapi2_conv_test.go @@ -1,13 +1,13 @@ -package openapi2conv +package openapi2conv_test import ( - "context" "encoding/json" "testing" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi2" + "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" ) @@ -20,11 +20,11 @@ func TestConvOpenAPIV3ToV2(t *testing.T) { sl := openapi3.NewLoader() err = sl.ResolveRefsIn(&doc3, nil) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) } - doc2, err := FromV3(&doc3) + doc2, err := openapi2conv.FromV3(&doc3) require.NoError(t, err) data, err := json.Marshal(doc2) require.NoError(t, err) @@ -40,11 +40,11 @@ func TestConvOpenAPIV3ToV2WithReqBody(t *testing.T) { sl := openapi3.NewLoader() err = sl.ResolveRefsIn(&doc3, nil) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) } - doc2, err := FromV3(&doc3) + doc2, err := openapi2conv.FromV3(&doc3) require.NoError(t, err) data, err := json.Marshal(doc2) require.NoError(t, err) @@ -56,9 +56,9 @@ func TestConvOpenAPIV2ToV3(t *testing.T) { err := json.Unmarshal([]byte(exampleV2), &doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) data, err := json.Marshal(doc3) require.NoError(t, err) @@ -117,9 +117,9 @@ func TestConvOpenAPIV2ToV3WithAdditionalPropertiesSchemaRef(t *testing.T) { err := json.Unmarshal(v2, &doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) responseSchema := doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value @@ -182,9 +182,9 @@ func TestConvOpenAPIV2ToV3WithNestedAdditionalPropertiesSchemaRef(t *testing.T) err := json.Unmarshal(v2, &doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) responseSchema := doc3.Paths.Value("/foo").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value @@ -261,9 +261,9 @@ func TestConvOpenAPIV2ToV3WithAllOfInsideAdditionalProperties(t *testing.T) { err := json.Unmarshal(v2, &doc2) require.NoError(t, err) - doc3, err := ToV3(&doc2) + doc3, err := openapi2conv.ToV3(&doc2) require.NoError(t, err) - err = doc3.Validate(context.Background()) + err = doc3.Validate(t.Context()) require.NoError(t, err) responseSchema := doc3.Paths.Value("/v1/objStatus").Get.Responses.Value("200").Value.Content.Get("application/json").Schema.Value diff --git a/openapi3/additionalProperties_test.go b/openapi3/additionalProperties_test.go index 46cf0dbe8..ac55b1d30 100644 --- a/openapi3/additionalProperties_test.go +++ b/openapi3/additionalProperties_test.go @@ -2,18 +2,16 @@ package openapi3_test import ( "bytes" - "context" "os" "testing" - "github.com/oasdiff/yaml3" + yaml "github.com/oasdiff/yaml3" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" ) func TestMarshalAdditionalProperties(t *testing.T) { - ctx := context.Background() data, err := os.ReadFile("testdata/test.openapi.additionalproperties.yml") require.NoError(t, err) @@ -22,7 +20,7 @@ func TestMarshalAdditionalProperties(t *testing.T) { spec, err := loader.LoadFromData(data) require.NoError(t, err) - err = spec.Validate(ctx) + err = spec.Validate(t.Context()) require.NoError(t, err) var buf bytes.Buffer @@ -35,6 +33,6 @@ func TestMarshalAdditionalProperties(t *testing.T) { spec2, err := loader.LoadFromData(buf.Bytes()) require.NoError(t, err) - err = spec2.Validate(ctx) + err = spec2.Validate(t.Context()) require.NoError(t, err) } diff --git a/openapi3/callback.go b/openapi3/callback.go index 8e68a3e73..407f730a9 100644 --- a/openapi3/callback.go +++ b/openapi3/callback.go @@ -2,7 +2,6 @@ package openapi3 import ( "context" - "slices" ) // Callback is specified by OpenAPI/Swagger standard version 3. @@ -39,19 +38,14 @@ func WithCallback(cb string, pathItem *PathItem) NewCallbackOption { func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - keys := make([]string, 0, callback.Len()) - for key := range callback.Map() { - keys = append(keys, key) - } - slices.Sort(keys) - for _, key := range keys { + for _, key := range callback.Keys() { v := callback.Value(key) if err := v.Validate(ctx); err != nil { return err } } - return validateExtensions(ctx, callback.Extensions) + return validateExtensions(ctx, callback.Extensions, callback.Origin) } // UnmarshalJSON sets Callbacks to a copy of data. diff --git a/openapi3/components.go b/openapi3/components.go index 3b40f24e3..b4b69fafb 100644 --- a/openapi3/components.go +++ b/openapi3/components.go @@ -4,22 +4,20 @@ import ( "context" "encoding/json" "fmt" - "slices" + "maps" "github.com/go-openapi/jsonpointer" ) -type ( - Callbacks map[string]*CallbackRef - Examples map[string]*ExampleRef - Headers map[string]*HeaderRef - Links map[string]*LinkRef - ParametersMap map[string]*ParameterRef - RequestBodies map[string]*RequestBodyRef - ResponseBodies map[string]*ResponseRef - Schemas map[string]*SchemaRef - SecuritySchemes map[string]*SecuritySchemeRef -) +type Callbacks map[string]*CallbackRef // Callbacks represents components' named callbacks +type Examples map[string]*ExampleRef // Examples represents components' named examples +type Headers map[string]*HeaderRef // Headers represents components' named headers +type Links map[string]*LinkRef // Links represents components' named links +type ParametersMap map[string]*ParameterRef // ParametersMap represents components' named parameters +type RequestBodies map[string]*RequestBodyRef // RequestBodies represents components' named request bodies +type ResponseBodies map[string]*ResponseRef // ResponseBodies represents components' named response bodies +type Schemas map[string]*SchemaRef // Schemas represents components' named schemas +type SecuritySchemes map[string]*SecuritySchemeRef // SecuritySchemes represents components' named security schemes // Components is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object @@ -54,9 +52,7 @@ func (components Components) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Components. func (components Components) MarshalYAML() (any, error) { m := make(map[string]any, 9+len(components.Extensions)) - for k, v := range components.Extensions { - m[k] = v - } + maps.Copy(m, components.Extensions) if x := components.Schemas; len(x) != 0 { m["schemas"] = x } @@ -112,145 +108,86 @@ func (components *Components) UnmarshalJSON(data []byte) error { } // Validate returns an error if Components does not comply with the OpenAPI spec. -func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) { +func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - - schemas := make([]string, 0, len(components.Schemas)) - for name := range components.Schemas { - schemas = append(schemas, name) - } - slices.Sort(schemas) - for _, k := range schemas { - v := components.Schemas[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("schema %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("schema %q: %w", k, err) + me := newErrCollector(ctx) + + validateMap := func(label string, names []string, validate func(k string) error) error { + for _, k := range names { + if idErr := ValidateIdentifier(k); idErr != nil { + if err := me.emit(&ComponentValidationError{Section: label, Name: k, Cause: idErr}); err != nil { + return err + } + // Skip validating the component's value when its name is + // invalid: any leaf error from validate(k) would surface as + // ": " and has no resolution path + // until the name is fixed. The continue keeps the noise + // per component bounded to a single, actionable finding. + continue + } + wrap := func(e error) error { return &ComponentValidationError{Section: label, Name: k, Cause: e} } + if err := me.emitWrapped(wrap, validate(k)); err != nil { + return err + } } + return nil } - parameters := make([]string, 0, len(components.Parameters)) - for name := range components.Parameters { - parameters = append(parameters, name) - } - slices.Sort(parameters) - for _, k := range parameters { - v := components.Parameters[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("parameter %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("parameter %q: %w", k, err) - } + if err := validateMap("schema", componentNames(components.Schemas), func(k string) error { + return components.Schemas[k].Validate(ctx) + }); err != nil { + return err } - requestBodies := make([]string, 0, len(components.RequestBodies)) - for name := range components.RequestBodies { - requestBodies = append(requestBodies, name) - } - slices.Sort(requestBodies) - for _, k := range requestBodies { - v := components.RequestBodies[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("request body %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("request body %q: %w", k, err) - } + if err := validateMap("parameter", componentNames(components.Parameters), func(k string) error { + return components.Parameters[k].Validate(ctx) + }); err != nil { + return err } - responses := make([]string, 0, len(components.Responses)) - for name := range components.Responses { - responses = append(responses, name) - } - slices.Sort(responses) - for _, k := range responses { - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("response %q: %w", k, err) - } - v := components.Responses[k] - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("response %q: %w", k, err) - } + if err := validateMap("request body", componentNames(components.RequestBodies), func(k string) error { + return components.RequestBodies[k].Validate(ctx) + }); err != nil { + return err } - headers := make([]string, 0, len(components.Headers)) - for name := range components.Headers { - headers = append(headers, name) - } - slices.Sort(headers) - for _, k := range headers { - v := components.Headers[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("header %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("header %q: %w", k, err) - } + if err := validateMap("response", componentNames(components.Responses), func(k string) error { + return components.Responses[k].Validate(ctx) + }); err != nil { + return err } - securitySchemes := make([]string, 0, len(components.SecuritySchemes)) - for name := range components.SecuritySchemes { - securitySchemes = append(securitySchemes, name) - } - slices.Sort(securitySchemes) - for _, k := range securitySchemes { - v := components.SecuritySchemes[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("security scheme %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("security scheme %q: %w", k, err) - } + if err := validateMap("header", componentNames(components.Headers), func(k string) error { + return components.Headers[k].Validate(ctx) + }); err != nil { + return err } - examples := make([]string, 0, len(components.Examples)) - for name := range components.Examples { - examples = append(examples, name) - } - slices.Sort(examples) - for _, k := range examples { - v := components.Examples[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("example %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("example %q: %w", k, err) - } + if err := validateMap("security scheme", componentNames(components.SecuritySchemes), func(k string) error { + return components.SecuritySchemes[k].Validate(ctx) + }); err != nil { + return err } - links := make([]string, 0, len(components.Links)) - for name := range components.Links { - links = append(links, name) - } - slices.Sort(links) - for _, k := range links { - v := components.Links[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("link %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("link %q: %w", k, err) - } + if err := validateMap("example", componentNames(components.Examples), func(k string) error { + return components.Examples[k].Validate(ctx) + }); err != nil { + return err } - callbacks := make([]string, 0, len(components.Callbacks)) - for name := range components.Callbacks { - callbacks = append(callbacks, name) + if err := validateMap("link", componentNames(components.Links), func(k string) error { + return components.Links[k].Validate(ctx) + }); err != nil { + return err } - slices.Sort(callbacks) - for _, k := range callbacks { - v := components.Callbacks[k] - if err = ValidateIdentifier(k); err != nil { - return fmt.Errorf("callback %q: %w", k, err) - } - if err = v.Validate(ctx); err != nil { - return fmt.Errorf("callback %q: %w", k, err) - } + + if err := validateMap("callback", componentNames(components.Callbacks), func(k string) error { + return components.Callbacks[k].Validate(ctx) + }); err != nil { + return err } - return validateExtensions(ctx, components.Extensions) + return me.finalize(validateExtensions(ctx, components.Extensions, components.Origin)) } var _ jsonpointer.JSONPointable = (*Schemas)(nil) diff --git a/openapi3/contact.go b/openapi3/contact.go index 24633aeaf..4a7bb3059 100644 --- a/openapi3/contact.go +++ b/openapi3/contact.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "encoding/json" + "maps" ) // Contact is specified by OpenAPI/Swagger standard version 3. @@ -28,9 +29,7 @@ func (contact Contact) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Contact. func (contact Contact) MarshalYAML() (any, error) { m := make(map[string]any, 3+len(contact.Extensions)) - for k, v := range contact.Extensions { - m[k] = v - } + maps.Copy(m, contact.Extensions) if x := contact.Name; x != "" { m["name"] = x } @@ -66,5 +65,5 @@ func (contact *Contact) UnmarshalJSON(data []byte) error { func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - return validateExtensions(ctx, contact.Extensions) + return validateExtensions(ctx, contact.Extensions, contact.Origin) } diff --git a/openapi3/content.go b/openapi3/content.go index 0a6624a2d..8bf7921a5 100644 --- a/openapi3/content.go +++ b/openapi3/content.go @@ -2,7 +2,6 @@ package openapi3 import ( "context" - "slices" "strings" ) @@ -10,7 +9,7 @@ import ( type Content map[string]*MediaType func NewContent() Content { - return make(map[string]*MediaType) + return make(Content) } func NewContentWithSchema(schema *Schema, consumes []string) Content { @@ -109,14 +108,8 @@ func (content Content) Get(mime string) *MediaType { func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - keys := make([]string, 0, len(content)) - for key := range content { - keys = append(keys, key) - } - slices.Sort(keys) - for _, k := range keys { - v := content[k] - if err := v.Validate(ctx); err != nil { + for _, k := range componentNames(content) { + if err := content[k].Validate(ctx); err != nil { return err } } diff --git a/openapi3/datetime_schema_test.go b/openapi3/datetime_schema_test.go index fec605765..8142ecb71 100644 --- a/openapi3/datetime_schema_test.go +++ b/openapi3/datetime_schema_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) var DateSpec = []byte(` @@ -47,7 +49,7 @@ info: `[1:]) func TestDateZeroMonth(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateSpec) require.NoError(t, err) @@ -58,11 +60,11 @@ func TestDateZeroMonth(t *testing.T) { "name": "kin-openapi", "date": "2001-00-03", }) - require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`) + require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+openapi3.FormatOfStringDate+`"`) } func TestDateZeroDay(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateSpec) require.NoError(t, err) @@ -73,11 +75,11 @@ func TestDateZeroDay(t *testing.T) { "name": "kin-openapi", "date": "2001-02-00", }) - require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+FormatOfStringDate+`"`) + require.EqualError(t, err, `Error at "/date": string doesn't match the format "date": string doesn't match pattern "`+openapi3.FormatOfStringDate+`"`) } func TestDateTimeZeroMonth(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -88,11 +90,11 @@ func TestDateTimeZeroMonth(t *testing.T) { "name": "kin-openapi", "datetime": "2001-00-03T04:05:06.789Z", }) - require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } func TestDateTimeZeroDay(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -103,11 +105,11 @@ func TestDateTimeZeroDay(t *testing.T) { "name": "kin-openapi", "datetime": "2001-02-00T04:05:06.789Z", }) - require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } func TestDateTimeLeapSecond(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -122,7 +124,7 @@ func TestDateTimeLeapSecond(t *testing.T) { } func TestDateTimeHourOutOfBounds(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -133,11 +135,11 @@ func TestDateTimeHourOutOfBounds(t *testing.T) { "name": "kin-openapi", "datetime": "2016-12-31T24:00:00.000Z", }) - require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } func TestDateTimeMinuteOutOfBounds(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -148,11 +150,11 @@ func TestDateTimeMinuteOutOfBounds(t *testing.T) { "name": "kin-openapi", "datetime": "2016-12-31T23:60:00.000Z", }) - require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } func TestDateTimeSecondOutOfBounds(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(DateTimeSpec) require.NoError(t, err) @@ -163,5 +165,5 @@ func TestDateTimeSecondOutOfBounds(t *testing.T) { "name": "kin-openapi", "datetime": "2016-12-31T23:59:61.000Z", }) - require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/datetime": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go index e2bbeda87..d797701c3 100644 --- a/openapi3/discriminator.go +++ b/openapi3/discriminator.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "encoding/json" + "maps" ) // Discriminator is specified by OpenAPI/Swagger standard version 3. @@ -41,9 +42,7 @@ func (discriminator Discriminator) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Discriminator. func (discriminator Discriminator) MarshalYAML() (any, error) { m := make(map[string]any, 2+len(discriminator.Extensions)) - for k, v := range discriminator.Extensions { - m[k] = v - } + maps.Copy(m, discriminator.Extensions) m["propertyName"] = discriminator.PropertyName if x := discriminator.Mapping; len(x) != 0 { m["mapping"] = x @@ -73,5 +72,5 @@ func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - return validateExtensions(ctx, discriminator.Extensions) + return validateExtensions(ctx, discriminator.Extensions, discriminator.Origin) } diff --git a/openapi3/discriminator_mapping_refs_test.go b/openapi3/discriminator_mapping_refs_test.go index 19579e8cb..499fbcd22 100644 --- a/openapi3/discriminator_mapping_refs_test.go +++ b/openapi3/discriminator_mapping_refs_test.go @@ -1,11 +1,12 @@ -package openapi3 +package openapi3_test import ( - "context" "strings" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) // TestDiscriminatorMappingRefsInternalize tests that discriminator mapping refs @@ -13,10 +14,10 @@ import ( // This test demonstrates the issue that discriminator mapping values, which are // JSON schema references serialized as plain strings, are not handled by InternalizeRefs. func TestDiscriminatorMappingRefsInternalize(t *testing.T) { - ctx := context.Background() + ctx := t.Context() // Load the spec with external discriminator mapping refs - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromFile("testdata/discriminator.yml") require.NoError(t, err, "loading test file") diff --git a/openapi3/discriminator_test.go b/openapi3/discriminator_test.go index d56791439..87e93d0b5 100644 --- a/openapi3/discriminator_test.go +++ b/openapi3/discriminator_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestParsingDiscriminator(t *testing.T) { @@ -45,7 +47,7 @@ func TestParsingDiscriminator(t *testing.T) { } ` - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData([]byte(spec)) require.NoError(t, err) diff --git a/openapi3/doc.go b/openapi3/doc.go index 41c9965c6..4179030b5 100644 --- a/openapi3/doc.go +++ b/openapi3/doc.go @@ -1,4 +1,25 @@ // Package openapi3 parses and writes OpenAPI 3 specification documents. // -// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md +// Supports both OpenAPI 3.0 and OpenAPI 3.1: +// - OpenAPI 3.0.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md +// - OpenAPI 3.1.x: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md +// +// OpenAPI 3.1 Features: +// - Type arrays with null support (e.g., ["string", "null"]) +// - JSON Schema 2020-12 keywords (const, examples, prefixItems, etc.) +// - Webhooks for defining callback operations +// - JSON Schema dialect specification +// - SPDX license identifiers +// +// The implementation maintains 100% backward compatibility with OpenAPI 3.0. +// +// For OpenAPI 3.1 validation, use the JSON Schema 2020-12 validator option: +// +// schema.VisitJSON(value, openapi3.EnableJSONSchema2020()) +// +// Version detection is available via helper methods: +// +// if doc.IsOpenAPI31OrLater() { +// // Handle OpenAPI 3.1 specific features +// } package openapi3 diff --git a/openapi3/encoding.go b/openapi3/encoding.go index 2c23eef8d..fd9316ae2 100644 --- a/openapi3/encoding.go +++ b/openapi3/encoding.go @@ -3,8 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "fmt" - "slices" + "maps" ) // Encoding is specified by OpenAPI/Swagger 3.0 standard. @@ -61,9 +60,7 @@ func (encoding Encoding) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Encoding. func (encoding Encoding) MarshalYAML() (any, error) { m := make(map[string]any, 5+len(encoding.Extensions)) - for k, v := range encoding.Extensions { - m[k] = v - } + maps.Copy(m, encoding.Extensions) if x := encoding.ContentType; x != "" { m["contentType"] = x } @@ -126,12 +123,7 @@ func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption return nil } - headers := make([]string, 0, len(encoding.Headers)) - for k := range encoding.Headers { - headers = append(headers, k) - } - slices.Sort(headers) - for _, k := range headers { + for _, k := range componentNames(encoding.Headers) { v := encoding.Headers[k] if err := ValidateIdentifier(k); err != nil { return nil @@ -152,8 +144,8 @@ func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption sm.Style == SerializationPipeDelimited && !sm.Explode, sm.Style == SerializationDeepObject && sm.Explode: default: - return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) + return newInvalidSerializationMethod("media type", sm.Style, sm.Explode, encoding.Origin) } - return validateExtensions(ctx, encoding.Extensions) + return validateExtensions(ctx, encoding.Extensions, encoding.Origin) } diff --git a/openapi3/encoding_test.go b/openapi3/encoding_test.go index 8f256a345..2afe4d1c4 100644 --- a/openapi3/encoding_test.go +++ b/openapi3/encoding_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "encoding/json" "testing" @@ -15,17 +14,17 @@ func TestEncodingJSON(t *testing.T) { require.NotEmpty(t, data) t.Log("Unmarshal *openapi3.Encoding from JSON") - docA := &Encoding{} - err = json.Unmarshal(encodingJSON, &docA) + enc := &Encoding{} + err = json.Unmarshal(encodingJSON, &enc) require.NoError(t, err) - require.NotEmpty(t, docA) + require.NotEmpty(t, enc) t.Log("Validate *openapi3.Encoding") - err = docA.Validate(context.Background()) + err = enc.Validate(t.Context()) require.NoError(t, err) t.Log("Ensure representations match") - dataA, err := json.Marshal(docA) + dataA, err := json.Marshal(enc) require.NoError(t, err) require.JSONEq(t, string(data), string(encodingJSON)) require.JSONEq(t, string(data), string(dataA)) diff --git a/openapi3/error_collector.go b/openapi3/error_collector.go new file mode 100644 index 000000000..b7331a7c3 --- /dev/null +++ b/openapi3/error_collector.go @@ -0,0 +1,82 @@ +package openapi3 + +import "context" + +// errCollector aggregates validation errors inside a Validate method. +// +// When multi-error mode is enabled (EnableMultiError), emit records the error +// and returns nil so the caller continues to the next sibling; if the error is +// itself a MultiError, its leaves are appended individually so the result is a +// flat MultiError of fully-wrapped problems (this matches what most consumers +// expect: one MultiError entry per independent problem). +// +// When multi-error mode is off, emit returns the error unchanged so the caller +// fails fast, preserving the historical behavior byte-for-byte. +// +// emitWrapped applies wrap to err, distributing wrap over each leaf when err +// is a MultiError. This is how validators attach per-section / per-path / +// per-operation context to each aggregated leaf. +// +// result returns the accumulated MultiError, or nil if none were recorded. +type errCollector struct { + multi bool + errs MultiError +} + +func newErrCollector(ctx context.Context) *errCollector { + return &errCollector{multi: getValidationOptions(ctx).multiErrorEnabled} +} + +func (c *errCollector) emit(err error) error { + if err == nil { + return nil + } + if !c.multi { + return err + } + if me, ok := err.(MultiError); ok { + for _, sub := range me { + if e := c.emit(sub); e != nil { + return e + } + } + return nil + } + c.errs = append(c.errs, err) + return nil +} + +func (c *errCollector) emitWrapped(wrap func(error) error, err error) error { + if err == nil { + return nil + } + if !c.multi { + return wrap(err) + } + if me, ok := err.(MultiError); ok { + for _, sub := range me { + if e := c.emitWrapped(wrap, sub); e != nil { + return e + } + } + return nil + } + return c.emit(wrap(err)) +} + +func (c *errCollector) result() error { + if len(c.errs) > 0 { + return c.errs + } + return nil +} + +// finalize emits err (typically the last sibling validation in a container, +// e.g. the extensions check) and returns the accumulated result. It collapses +// the trailing emit-then-result pattern into a single line at each call site. +func (c *errCollector) finalize(err error) error { + if e := c.emit(err); e != nil { + return e + } + return c.result() +} diff --git a/openapi3/example.go b/openapi3/example.go index 5cb19a739..eeb4c6d4f 100644 --- a/openapi3/example.go +++ b/openapi3/example.go @@ -3,7 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" + "maps" ) // Example is specified by OpenAPI/Swagger 3.0 standard. @@ -34,9 +34,7 @@ func (example Example) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Example. func (example Example) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(example.Extensions)) - for k, v := range example.Extensions { - m[k] = v - } + maps.Copy(m, example.Extensions) if x := example.Summary; x != "" { m["summary"] = x } @@ -76,13 +74,13 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) ctx = WithValidationOptions(ctx, opts...) if example.Value != nil && example.ExternalValue != "" { - return errors.New("value and externalValue are mutually exclusive") + return newExampleValueExternalValueExclusive(example.Origin) } if example.Value == nil && example.ExternalValue == "" { - return errors.New("no value or externalValue field") + return newExampleValueOrExternalValueRequired(example.Origin) } - return validateExtensions(ctx, example.Extensions) + return validateExtensions(ctx, example.Extensions, example.Origin) } // UnmarshalJSON sets Examples to a copy of data. diff --git a/openapi3/example_jsonschema2020_test.go b/openapi3/example_jsonschema2020_test.go new file mode 100644 index 000000000..e3542c6f4 --- /dev/null +++ b/openapi3/example_jsonschema2020_test.go @@ -0,0 +1,254 @@ +package openapi3_test + +import ( + "fmt" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Example demonstrates how to enable and use the JSON Schema 2020-12 validator +// with OpenAPI 3.1 features. +func Example_jsonSchema2020Validator() { + // Enable JSON Schema 2020-12 validator + + // Create a schema using OpenAPI 3.1 features + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Examples: []any{ + "John Doe", + "Jane Smith", + }, + }, + }, + "age": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + // Type array with null - OpenAPI 3.1 feature + Type: &openapi3.Types{"integer", "null"}, + }, + }, + "status": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + // Const keyword - OpenAPI 3.1 feature + Const: "active", + }, + }, + }, + Required: []string{"name", "status"}, + } + + // Valid data + validData := map[string]any{ + "name": "John Doe", + "age": 30, + "status": "active", + } + + if err := schema.VisitJSON(validData, openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("validation failed:", err) + } else { + fmt.Println("valid data passed") + } + + // Valid with null age + validWithNull := map[string]any{ + "name": "Jane Smith", + "age": nil, // null is allowed in type array + "status": "active", + } + + if err := schema.VisitJSON(validWithNull, openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("validation failed:", err) + } else { + fmt.Println("valid data with null passed") + } + + // Invalid: wrong const value + invalidData := map[string]any{ + "name": "Bob Wilson", + "age": 25, + "status": "inactive", // should be "active" + } + + if err := schema.VisitJSON(invalidData, openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("invalid data rejected") + } + + // Output: + // valid data passed + // valid data with null passed + // invalid data rejected +} + +// Example demonstrates type arrays with null support +func Example_typeArrayWithNull() { + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + } + + // Both string and null are valid + if err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("string accepted") + } + + if err := schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("null accepted") + } + + if err := schema.VisitJSON(123, openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("number rejected") + } + + // Output: + // string accepted + // null accepted + // number rejected +} + +// Example demonstrates the const keyword +func Example_constKeyword() { + + schema := &openapi3.Schema{ + Const: "production", + } + + if err := schema.VisitJSON("production", openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("const value accepted") + } + + if err := schema.VisitJSON("development", openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("other value rejected") + } + + // Output: + // const value accepted + // other value rejected +} + +// Example demonstrates the examples field +func Example_examplesField() { + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + // Examples array - OpenAPI 3.1 feature + Examples: []any{ + "red", + "green", + "blue", + }, + } + + // Examples don't affect validation, any string is valid + if err := schema.VisitJSON("yellow", openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("any string accepted") + } + + // Output: + // any string accepted +} + +// Example demonstrates backward compatibility with nullable +func Example_nullableBackwardCompatibility() { + + // OpenAPI 3.0 style nullable + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + } + + // Automatically converted to type array ["string", "null"] + if err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("string accepted") + } + + if err := schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("null accepted") + } + + // Output: + // string accepted + // null accepted +} + +// Example demonstrates complex nested schemas +func Example_complexNestedSchema() { + + min := 0.0 + max := 100.0 + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "user": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + }, + }, + "email": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Format: "email", + }, + }, + }, + Required: []string{"name", "email"}, + }, + }, + "score": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"number"}, + Min: &min, + Max: &max, + }, + }, + }, + Required: []string{"user", "score"}, + } + + validData := map[string]any{ + "user": map[string]any{ + "name": "John Doe", + "email": "john@example.com", + }, + "score": 85.5, + } + + if err := schema.VisitJSON(validData, openapi3.EnableJSONSchema2020()); err == nil { + fmt.Println("complex nested object validated") + } + + // Output: + // complex nested object validated +} + +// Example demonstrates using both validators for comparison +func Example_comparingValidators() { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + MinLength: 5, + } + + testValue := "test" + + // Test with built-in validator (no option) + if err := schema.VisitJSON(testValue); err != nil { + fmt.Println("built-in validator: rejected") + } + + // Test with JSON Schema 2020-12 validator + if err := schema.VisitJSON(testValue, openapi3.EnableJSONSchema2020()); err != nil { + fmt.Println("visit JSON Schema 2020-12 validator: rejected") + } + + // Output: + // built-in validator: rejected + // visit JSON Schema 2020-12 validator: rejected +} diff --git a/openapi3/example_refs_test.go b/openapi3/example_refs_test.go index b2f36e3ea..5fe8e6955 100644 --- a/openapi3/example_refs_test.go +++ b/openapi3/example_refs_test.go @@ -3,8 +3,9 @@ package openapi3_test import ( "testing" - "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestParameterExampleRef(t *testing.T) { diff --git a/openapi3/example_validation.go b/openapi3/example_validation.go index 0d105c92d..1ecdcf3b8 100644 --- a/openapi3/example_validation.go +++ b/openapi3/example_validation.go @@ -3,14 +3,18 @@ package openapi3 import "context" func validateExampleValue(ctx context.Context, input any, schema *Schema) error { - opts := make([]SchemaValidationOption, 0, 2) + opts := []SchemaValidationOption{MultiErrors()} - if vo := getValidationOptions(ctx); vo.examplesValidationAsReq { + vo := getValidationOptions(ctx) + if vo.examplesValidationAsReq { opts = append(opts, VisitAsRequest()) } else if vo.examplesValidationAsRes { opts = append(opts, VisitAsResponse()) } - opts = append(opts, MultiErrors()) + + if vo.jsonSchema2020ValidationEnabled { + opts = append(opts, EnableJSONSchema2020()) + } return schema.VisitJSON(input, opts...) } diff --git a/openapi3/example_validation_test.go b/openapi3/example_validation_test.go index 8980a21e9..88595c79e 100644 --- a/openapi3/example_validation_test.go +++ b/openapi3/example_validation_test.go @@ -29,7 +29,7 @@ func TestExamplesSchemaValidation(t *testing.T) { param1example: value: abcd `, - errContains: `invalid paths: invalid path /user: invalid operation POST: param1example`, + errContains: `invalid paths: invalid path /user: invalid operation POST: invalid example: param1example`, }, { name: "valid_parameter_examples", @@ -67,7 +67,7 @@ func TestExamplesSchemaValidation(t *testing.T) { email: bad password: short `, - errContains: `invalid paths: invalid path /user: invalid operation POST: example BadUser`, + errContains: `invalid paths: invalid path /user: invalid operation POST: invalid example: example BadUser`, }, { name: "valid_component_examples", @@ -178,7 +178,7 @@ func TestExamplesSchemaValidation(t *testing.T) { password: password user_id: 4321 `, - errContains: `ReadWriteOnlyRequest: readOnly property "user_id" in request`, + errContains: `invalid example: example ReadWriteOnlyRequest: readOnly property "user_id" in request`, }, { name: "invalid_writeonly_response_examples", @@ -195,7 +195,7 @@ func TestExamplesSchemaValidation(t *testing.T) { user_id: 4321 `, - errContains: `ReadWriteOnlyResponse: writeOnly property "password" in response`, + errContains: `invalid example: example ReadWriteOnlyResponse: writeOnly property "password" in response`, }, } diff --git a/openapi3/extension.go b/openapi3/extension.go index 42400474e..e40502043 100644 --- a/openapi3/extension.go +++ b/openapi3/extension.go @@ -2,16 +2,21 @@ package openapi3 import ( "context" - "fmt" - "slices" "strings" ) -func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...) +// validateExtensions reports any non-`x-` keys in the given extensions +// map that are not explicitly allowed by the validation context. The +// origin argument is attached to the resulting ExtraSiblingFieldsError +// so callers can pin the finding to the parent object that carries the +// unknown keys; pass nil when the parent has no Origin (the loader was +// run with IncludeOrigin = false, or the parent was constructed +// programmatically without an Origin set). +func validateExtensions(ctx context.Context, extensions map[string]any, origin *Origin) error { // FIXME: newtype + Validate(...) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed var unknowns []string - for k := range extensions { + for _, k := range componentNames(extensions) { if strings.HasPrefix(k, "x-") { continue } @@ -24,8 +29,7 @@ func validateExtensions(ctx context.Context, extensions map[string]any) error { } if len(unknowns) != 0 { - slices.Sort(unknowns) - return fmt.Errorf("extra sibling fields: %+v", unknowns) + return newExtraSiblingFields(unknowns, origin) } return nil diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go index 4c0a56791..b15ce9ac2 100644 --- a/openapi3/external_docs.go +++ b/openapi3/external_docs.go @@ -3,8 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" - "fmt" + "maps" "net/url" ) @@ -30,9 +29,7 @@ func (e ExternalDocs) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of ExternalDocs. func (e ExternalDocs) MarshalYAML() (any, error) { m := make(map[string]any, 2+len(e.Extensions)) - for k, v := range e.Extensions { - m[k] = v - } + maps.Copy(m, e.Extensions) if x := e.Description; x != "" { m["description"] = x } @@ -62,13 +59,18 @@ func (e *ExternalDocs) UnmarshalJSON(data []byte) error { // Validate returns an error if ExternalDocs does not comply with the OpenAPI spec. func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if e.URL == "" { - return errors.New("url is required") + if err := me.emit(newExternalDocsURLRequired(e.Origin)); err != nil { + return err + } } if _, err := url.Parse(e.URL); err != nil { - return fmt.Errorf("url is incorrect: %w", err) + if err := me.emit(&ExternalDocsURLValidationError{Cause: err}); err != nil { + return err + } } - return validateExtensions(ctx, e.Extensions) + return me.finalize(validateExtensions(ctx, e.Extensions, e.Origin)) } diff --git a/openapi3/external_docs_test.go b/openapi3/external_docs_test.go index f2fb64f2e..b21b555c8 100644 --- a/openapi3/external_docs_test.go +++ b/openapi3/external_docs_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -31,7 +30,7 @@ func TestExternalDocs_Validate(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - err := tt.extDocs.Validate(context.Background()) + err := tt.extDocs.Validate(t.Context()) if tt.expectedErr != "" { require.EqualError(t, err, tt.expectedErr) } else { diff --git a/openapi3/header.go b/openapi3/header.go index a37bcc44e..466c8ea21 100644 --- a/openapi3/header.go +++ b/openapi3/header.go @@ -2,8 +2,6 @@ package openapi3 import ( "context" - "errors" - "fmt" "github.com/go-openapi/jsonpointer" ) @@ -54,10 +52,10 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er ctx = WithValidationOptions(ctx, opts...) if header.Name != "" { - return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map") + return newHeaderNameForbidden(header.Origin) } if header.In != "" { - return errors.New("header 'in' MUST NOT be specified, it is implicitly in header") + return newHeaderInForbidden(header.Origin) } // Validate a parameter's serialization method. @@ -68,28 +66,28 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er if smSupported := false || sm.Style == SerializationSimple && !sm.Explode || sm.Style == SerializationSimple && sm.Explode; !smSupported { - e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode) - return fmt.Errorf("header schema is invalid: %w", e) + e := newInvalidSerializationMethod("header", sm.Style, sm.Explode, header.Origin) + return &HeaderFieldValidationError{Field: "schema", Cause: e} } if (header.Schema == nil) == (len(header.Content) == 0) { - e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header) - return fmt.Errorf("header schema is invalid: %w", e) + return &HeaderFieldValidationError{Field: "schema", + Cause: newHeaderContentSchemaExactlyOne(header, header.Origin)} } if schema := header.Schema; schema != nil { if err := schema.Validate(ctx); err != nil { - return fmt.Errorf("header schema is invalid: %w", err) + return &HeaderFieldValidationError{Field: "schema", Cause: err} } } if content := header.Content; content != nil { - e := errors.New("parameter content must only contain one entry") if len(content) > 1 { - return fmt.Errorf("header content is invalid: %w", e) + return &HeaderFieldValidationError{Field: "content", + Cause: newHeaderContentSingleEntry(header.Origin)} } if err := content.Validate(ctx); err != nil { - return fmt.Errorf("header content is invalid: %w", err) + return &HeaderFieldValidationError{Field: "content", Cause: err} } } return nil diff --git a/openapi3/helpers.go b/openapi3/helpers.go index e95f75250..24c26a43a 100644 --- a/openapi3/helpers.go +++ b/openapi3/helpers.go @@ -230,7 +230,8 @@ func ReferencesComponentInRootDocument(doc *T, ref ComponentRef) (string, bool) // Case 2: // Something like: ../openapi.yaml#/components/schemas/myElement - for name, s := range components { + for _, name := range componentNames(components) { + s := components[name] // Must be a reference to a YAML file. if !isWholeDocumentReference(s.RefString()) { continue diff --git a/openapi3/info.go b/openapi3/info.go index 288235d11..e4f08e638 100644 --- a/openapi3/info.go +++ b/openapi3/info.go @@ -3,16 +3,18 @@ package openapi3 import ( "context" "encoding/json" - "errors" + "maps" ) // Info is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object +// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#info-object type Info struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` - Title string `json:"title" yaml:"title"` // Required + Title string `json:"title" yaml:"title"` // Required + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` // OpenAPI >=3.1 Description string `json:"description,omitempty" yaml:"description,omitempty"` TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` @@ -34,11 +36,12 @@ func (info *Info) MarshalYAML() (any, error) { if info == nil { return nil, nil } - m := make(map[string]any, 6+len(info.Extensions)) - for k, v := range info.Extensions { - m[k] = v - } + m := make(map[string]any, 7+len(info.Extensions)) + maps.Copy(m, info.Extensions) m["title"] = info.Title + if x := info.Summary; x != "" { + m["summary"] = x + } if x := info.Description; x != "" { m["description"] = x } @@ -64,6 +67,7 @@ func (info *Info) UnmarshalJSON(data []byte) error { } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "title") + delete(x.Extensions, "summary") delete(x.Extensions, "description") delete(x.Extensions, "termsOfService") delete(x.Extensions, "contact") @@ -79,26 +83,37 @@ func (info *Info) UnmarshalJSON(data []byte) error { // Validate returns an error if Info does not comply with the OpenAPI spec. func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) + + if info.Summary != "" && !getValidationOptions(ctx).isOpenAPI31OrLater { + if err := me.emit(newInfoSummaryFieldFor31Plus(info.Origin)); err != nil { + return err + } + } if contact := info.Contact; contact != nil { - if err := contact.Validate(ctx); err != nil { + if err := me.emit(contact.Validate(ctx)); err != nil { return err } } if license := info.License; license != nil { - if err := license.Validate(ctx); err != nil { + if err := me.emit(license.Validate(ctx)); err != nil { return err } } if info.Version == "" { - return errors.New("value of version must be a non-empty string") + if err := me.emit(newInfoVersionRequired(info.Origin)); err != nil { + return err + } } if info.Title == "" { - return errors.New("value of title must be a non-empty string") + if err := me.emit(newInfoTitleRequired(info.Origin)); err != nil { + return err + } } - return validateExtensions(ctx, info.Extensions) + return me.finalize(validateExtensions(ctx, info.Extensions, info.Origin)) } diff --git a/openapi3/info_test.go b/openapi3/info_test.go new file mode 100644 index 000000000..5c813b996 --- /dev/null +++ b/openapi3/info_test.go @@ -0,0 +1,46 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestValidateInfo_SummaryIn30(t *testing.T) { + spec := []byte(` +openapi: '3' +paths: {} +info: + title: An API + version: 1.2.3.4 + summary: bla +`) + + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData(spec) + require.NoError(t, err) + + err = doc.Validate(loader.Context) + require.ErrorContains(t, err, "invalid info") + require.ErrorContains(t, err, "field summary") +} + +func TestValidateInfo_SummaryIn31(t *testing.T) { + spec := []byte(` +openapi: '3.1' +paths: {} +info: + title: An API + version: 1.2.3.4 + summary: bla +`) + + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData(spec) + require.NoError(t, err) + + err = doc.Validate(loader.Context) + require.NoError(t, err) +} diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go index 2398bc64c..fe8b379eb 100644 --- a/openapi3/internalize_refs.go +++ b/openapi3/internalize_refs.go @@ -23,7 +23,7 @@ type RefNameResolver func(*T, ComponentRef) string // - Cutting the "#/components/" part. // - Cutting the file extensions (.yaml/.json) from documents. // - Trimming the common directory with the root spec. -// - Replace invalid characters with with underscores. +// - Replace invalid characters with underscores. // // This is an injective mapping over a "reasonable" amount of the possible openapi // spec domain space but is not perfect. There might be edge cases. @@ -34,7 +34,7 @@ func DefaultRefNameResolver(doc *T, ref ComponentRef) string { name := ref.RefPath() - // If refering to a component in the root spec, no need to internalize just use + // If referring to a component in the root spec, no need to internalize just use // the existing component. // XXX(percivalalb): since this function call is iterating over components behind the // scenes during an internalization call it actually starts interating over @@ -63,7 +63,7 @@ func DefaultRefNameResolver(doc *T, ref ComponentRef) string { filePath = "" } - // Remove the path extentions to make this JSON/YAML agnostic. + // Remove the path extensions to make this JSON/YAML agnostic. for ext := path.Ext(filePath); len(ext) > 0; ext = path.Ext(filePath) { filePath = strings.TrimSuffix(filePath, ext) } @@ -112,7 +112,7 @@ func cutDirectories(p, dirs string) (string, bool) { var sb strings.Builder sb.Grow(len(ParameterInHeader)) - for _, segments := range strings.Split(p, "/") { + for segments := range strings.SplitSeq(p, "/") { sb.WriteString(segments) if sb.String() == p { @@ -348,7 +348,8 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx // Discriminator mapping values are special cases since they are not full // ref objects but are string references to schema objects. if s.Discriminator != nil { - for k, mapRef := range s.Discriminator.Mapping { + for _, k := range componentNames(s.Discriminator.Mapping) { + mapRef := s.Discriminator.Mapping[k] s2 := (*SchemaRef)(&mapRef) isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go index 967ebca5e..9d085d8d2 100644 --- a/openapi3/internalize_refs_test.go +++ b/openapi3/internalize_refs_test.go @@ -1,16 +1,17 @@ -package openapi3 +package openapi3_test import ( - "context" "os" "regexp" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestInternalizeRefs(t *testing.T) { - ctx := context.Background() + ctx := t.Context() regexpRef := regexp.MustCompile(`"\$ref":`) regexpRefInternal := regexp.MustCompile(`"\$ref":"#`) @@ -31,7 +32,7 @@ func TestInternalizeRefs(t *testing.T) { for _, test := range tests { t.Run(test.filename, func(t *testing.T) { // Load in the reference spec from the testdata - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromFile(test.filename) require.NoError(t, err, "loading test file") diff --git a/openapi3/issue136_test.go b/openapi3/issue136_test.go index 3aa7edd8f..7be0f532c 100644 --- a/openapi3/issue136_test.go +++ b/openapi3/issue136_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue136(t *testing.T) { @@ -37,7 +39,7 @@ components: t.Run(testcase.dflt, func(t *testing.T) { spec := specf(testcase.dflt) - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(spec)) require.NoError(t, err) diff --git a/openapi3/issue230_test.go b/openapi3/issue230_test.go new file mode 100644 index 000000000..da4fe3b47 --- /dev/null +++ b/openapi3/issue230_test.go @@ -0,0 +1,620 @@ +package openapi3_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +// TestBackwardCompatibility_OpenAPI30 ensures that existing OpenAPI 3.0 functionality is not broken +func TestBackwardCompatibility_OpenAPI30(t *testing.T) { + t.Run("load and validate OpenAPI 3.0 document", func(t *testing.T) { + spec := ` +openapi: 3.0.3 +info: + title: Test API + version: 1.0.0 + license: + name: MIT + url: https://opensource.org/licenses/MIT +paths: + /users: + get: + summary: Get users + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + nullable: true + required: + - id +` + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + require.NotNil(t, doc) + + // Verify version detection + require.True(t, doc.IsOpenAPI30()) + require.False(t, doc.IsOpenAPI31OrLater()) + require.Equal(t, "3.0", doc.OpenAPIMajorMinor()) + + // Verify structure + require.Equal(t, "Test API", doc.Info.Title) + require.NotNil(t, doc.Info.License) + require.Equal(t, "MIT", doc.Info.License.Name) + require.Equal(t, "https://opensource.org/licenses/MIT", doc.Info.License.URL) + require.Empty(t, doc.Info.License.Identifier) // 3.0 doesn't have this + + // Verify webhooks is nil for 3.0 + require.Nil(t, doc.Webhooks) + require.Empty(t, doc.JSONSchemaDialect) + + // Validate + err = doc.Validate(t.Context()) + require.NoError(t, err) + }) + + t.Run("nullable schema validation still works", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + } + + // Should accept string + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Should accept null + err = schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Should reject number + err = schema.VisitJSON(123, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("existing schema fields work", func(t *testing.T) { + min := 0.0 + max := 100.0 + schema := &openapi3.Schema{ + Type: &openapi3.Types{"integer"}, + Min: &min, + Max: &max, + MinLength: 1, + } + + // Type checking + require.True(t, schema.Type.Is("integer")) + require.False(t, schema.Type.IsMultiple()) + + // Validation still works + err := schema.VisitJSON(50, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(150, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("serialization preserves 3.0 format", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "Test", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + } + + data, err := json.Marshal(doc) + require.NoError(t, err) + + // Should not contain 3.1 fields + require.NotContains(t, string(data), "webhooks") + require.NotContains(t, string(data), "jsonSchemaDialect") + require.Contains(t, string(data), `"openapi":"3.0.3"`) + }) +} + +// TestOpenAPI31_NewFeatures tests all new OpenAPI 3.1 features +func TestOpenAPI31_NewFeatures(t *testing.T) { + t.Run("load and validate OpenAPI 3.1 document with webhooks", func(t *testing.T) { + spec := ` +openapi: 3.1.0 +info: + title: Test API + version: 1.0.0 + license: + name: MIT + identifier: MIT +jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema +paths: + /users: + get: + responses: + '200': + description: Success +webhooks: + newUser: + post: + summary: User created notification + requestBody: + content: + application/json: + schema: + type: object + properties: + id: + type: integer + responses: + '200': + description: Processed +` + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + require.NotNil(t, doc) + + // Verify version detection + require.True(t, doc.IsOpenAPI31OrLater()) + require.False(t, doc.IsOpenAPI30()) + require.Equal(t, "3.1", doc.OpenAPIMajorMinor()) + + // Verify 3.1 fields + require.NotNil(t, doc.Webhooks) + require.Contains(t, doc.Webhooks, "newUser") + require.Equal(t, "https://json-schema.org/draft/2020-12/schema", doc.JSONSchemaDialect) + + // Verify license identifier + require.Equal(t, "MIT", doc.Info.License.Identifier) + + // Validate + err = doc.Validate(t.Context()) + require.NoError(t, err) + }) + + t.Run("type arrays with null", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + } + + // Type checks + require.True(t, schema.Type.IsMultiple()) + require.True(t, schema.Type.IncludesNull()) + require.True(t, schema.Type.Includes("string")) + + // Should accept string + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Should accept null (with new validator) + + err = schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Should reject number + err = schema.VisitJSON(123, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("const keyword validation", func(t *testing.T) { + + schema := &openapi3.Schema{ + Const: "production", + } + + err := schema.VisitJSON("production", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON("development", openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("examples array", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Examples: []any{ + "example1", + "example2", + "example3", + }, + } + + require.Len(t, schema.Examples, 3) + + // Serialize and verify + data, err := json.Marshal(schema) + require.NoError(t, err) + require.Contains(t, string(data), "examples") + require.Contains(t, string(data), "example1") + }) + + t.Run("all new schema keywords serialize", func(t *testing.T) { + minContains := uint64(1) + maxContains := uint64(3) + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + PrefixItems: openapi3.SchemaRefs{ + {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + {Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + }, + Contains: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + MinContains: &minContains, + MaxContains: &maxContains, + PropertyNames: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Pattern: "^[a-z]+$"}, + }, + } + + data, err := json.Marshal(schema) + require.NoError(t, err) + + str := string(data) + require.Contains(t, str, "prefixItems") + require.Contains(t, str, "contains") + require.Contains(t, str, "minContains") + require.Contains(t, str, "maxContains") + require.Contains(t, str, "propertyNames") + }) + + t.Run("round-trip serialization preserves all fields", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + JSONSchemaDialect: "https://json-schema.org/draft/2020-12/schema", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + License: &openapi3.License{ + Name: "Apache-2.0", + Identifier: "Apache-2.0", + }, + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "test": { + Post: &openapi3.Operation{ + Summary: "Test webhook", + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Description: openapi3.Ptr("OK"), + }, + }), + ), + }, + }, + }, + } + + // Serialize + data, err := json.Marshal(doc) + require.NoError(t, err) + + // Deserialize + var doc2 openapi3.T + err = json.Unmarshal(data, &doc2) + require.NoError(t, err) + + // Verify all fields + require.Equal(t, "3.1.0", doc2.OpenAPI) + require.Equal(t, "https://json-schema.org/draft/2020-12/schema", doc2.JSONSchemaDialect) + require.Equal(t, "Apache-2.0", doc2.Info.License.Identifier) + require.NotNil(t, doc2.Webhooks) + require.Contains(t, doc2.Webhooks, "test") + }) +} + +// TestJSONSchema2020Validator_RealWorld tests the validator with realistic schemas +func TestJSONSchema2020Validator_RealWorld(t *testing.T) { + + t.Run("complex nested object with nullable", func(t *testing.T) { + min := 0.0 + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "user": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "id": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}, + }, + "name": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + }, + }, + "age": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"integer"}, + Min: &min, + }, + }, + }, + Required: []string{"id"}, + }, + }, + "tags": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"array", "null"}, + Items: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + }, + }, + }, + Required: []string{"user"}, + } + + // Valid data + validData := map[string]any{ + "user": map[string]any{ + "id": 1, + "name": "John", + "age": 30, + }, + "tags": []any{"tag1", "tag2"}, + } + err := schema.VisitJSON(validData, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Valid with null name + validDataNullName := map[string]any{ + "user": map[string]any{ + "id": 2, + "name": nil, + "age": 25, + }, + "tags": nil, + } + err = schema.VisitJSON(validDataNullName, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Invalid - missing required field + invalidData := map[string]any{ + "user": map[string]any{ + "name": "Jane", + }, + } + err = schema.VisitJSON(invalidData, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("oneOf with different types", func(t *testing.T) { + schema := &openapi3.Schema{ + OneOf: openapi3.SchemaRefs{ + &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "type": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Const: "email", + }, + }, + "email": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + }, + Required: []string{"type", "email"}, + }, + }, + &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "type": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Const: "phone", + }, + }, + "phone": &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + }, + Required: []string{"type", "phone"}, + }, + }, + }, + } + + // Valid email + emailData := map[string]any{ + "type": "email", + "email": "test@example.com", + } + err := schema.VisitJSON(emailData, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Valid phone + phoneData := map[string]any{ + "type": "phone", + "phone": "+1234567890", + } + err = schema.VisitJSON(phoneData, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Invalid - doesn't match any oneOf + invalidData := map[string]any{ + "type": "email", + // missing email field + } + err = schema.VisitJSON(invalidData, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) +} + +// TestMigrationScenarios tests realistic migration paths +func TestMigrationScenarios(t *testing.T) { + t.Run("migrate nullable to type array", func(t *testing.T) { + // Old 3.0 style + schema30 := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + } + + // New 3.1 style + schema31 := &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + } + + // Both should accept null with new validator + + err := schema30.VisitJSON(nil) + require.NoError(t, err) + + err = schema31.VisitJSON(nil) + require.NoError(t, err) + + // Both should accept string + err = schema30.VisitJSON("test") + require.NoError(t, err) + + err = schema31.VisitJSON("test") + require.NoError(t, err) + }) + + t.Run("automatic version detection and configuration", func(t *testing.T) { + // Simulate loading 3.0 document + spec30 := []byte(`{"openapi":"3.0.3","info":{"title":"Test","version":"1.0.0"},"paths":{}}`) + var doc30 openapi3.T + err := json.Unmarshal(spec30, &doc30) + require.NoError(t, err) + + if doc30.IsOpenAPI31OrLater() { + } + + // Simulate loading 3.1 document + spec31 := []byte(`{"openapi":"3.1.0","info":{"title":"Test","version":"1.0.0"},"paths":{}}`) + var doc31 openapi3.T + err = json.Unmarshal(spec31, &doc31) + require.NoError(t, err) + + if doc31.IsOpenAPI31OrLater() { + } + + // Cleanup + }) +} + +// TestEdgeCases tests edge cases and error conditions +func TestEdgeCases(t *testing.T) { + t.Run("empty types array", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{}, + } + + require.True(t, schema.Type.IsEmpty()) + require.False(t, schema.Type.IsSingle()) + require.False(t, schema.Type.IsMultiple()) + }) + + t.Run("nil vs empty webhooks", func(t *testing.T) { + doc30 := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "Test", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + } + + doc31Empty := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{Title: "Test", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{}, + } + + // Nil webhooks should not serialize + data30, err := json.Marshal(doc30) + require.NoError(t, err) + require.NotContains(t, string(data30), "webhooks") + + // Empty webhooks should not serialize + data31, err := json.Marshal(doc31Empty) + require.NoError(t, err) + require.NotContains(t, string(data31), "webhooks") + }) + + t.Run("license with both url and identifier", func(t *testing.T) { + license := &openapi3.License{ + Name: "MIT", + URL: "https://opensource.org/licenses/MIT", + Identifier: "MIT", + } + + // Should serialize both (spec says only one should be used, but library allows both) + data, err := json.Marshal(license) + require.NoError(t, err) + require.Contains(t, string(data), `"url"`) + require.Contains(t, string(data), `"identifier"`) + }) + + t.Run("schema without type permits any type", func(t *testing.T) { + schema := &openapi3.Schema{} + + require.True(t, schema.Type.Permits("string")) + require.True(t, schema.Type.Permits("number")) + require.True(t, schema.Type.Permits("anything")) + }) +} + +// TestPerformance checks for obvious performance issues +func TestPerformance(t *testing.T) { + t.Run("large schema compilation", func(t *testing.T) { + + // Create a large schema + properties := make(openapi3.Schemas) + for i := range 100 { + properties[string(rune('a'+i%26))+string(rune('0'+i/26))] = &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + }, + } + } + + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: properties, + } + + // Should compile and validate without hanging + data := map[string]any{"a0": "test"} + err := schema.VisitJSON(data, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + }) + + t.Run("deeply nested schema", func(t *testing.T) { + // Create deeply nested schema (but not too deep to cause stack overflow) + schema := &openapi3.Schema{Type: &openapi3.Types{"object"}} + current := schema + + for range 10 { + current.Properties = openapi3.Schemas{ + "nested": &openapi3.SchemaRef{ + Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + }, + }, + } + current = current.Properties["nested"].Value + } + + // Should serialize without issue + _, err := json.Marshal(schema) + require.NoError(t, err) + }) +} diff --git a/openapi3/issue241_test.go b/openapi3/issue241_test.go index 9a643ede4..caf9a7e08 100644 --- a/openapi3/issue241_test.go +++ b/openapi3/issue241_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/oasdiff/yaml3" + yaml "github.com/oasdiff/yaml3" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" diff --git a/openapi3/issue301_test.go b/openapi3/issue301_test.go index cf5350d5f..7281b30f4 100644 --- a/openapi3/issue301_test.go +++ b/openapi3/issue301_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue301(t *testing.T) { - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromFile("testdata/callbacks.yml") @@ -16,7 +18,7 @@ func TestIssue301(t *testing.T) { err = doc.Validate(sl.Context) require.NoError(t, err) - require.Equal(t, &Types{"object"}, doc. + require.Equal(t, &openapi3.Types{"object"}, doc. Paths.Value("/trans"). Post.Callbacks["transactionCallback"].Value. Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"). @@ -24,7 +26,7 @@ func TestIssue301(t *testing.T) { Content["application/json"].Schema.Value. Type) - require.Equal(t, &Types{"boolean"}, doc. + require.Equal(t, &openapi3.Types{"boolean"}, doc. Paths.Value("/other"). Post.Callbacks["myEvent"].Value. Value("{$request.query.queryUrl}"). diff --git a/openapi3/issue341_test.go b/openapi3/issue341_test.go index 55f18e54d..ee55138a2 100644 --- a/openapi3/issue341_test.go +++ b/openapi3/issue341_test.go @@ -1,14 +1,15 @@ -package openapi3 +package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue341(t *testing.T) { - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromFile("testdata/main.yaml") require.NoError(t, err) @@ -34,7 +35,7 @@ func TestIssue341(t *testing.T) { } }`, string(bs)) - require.Equal(t, &Types{"string"}, doc. + require.Equal(t, &openapi3.Types{"string"}, doc. Paths.Value("/testpath"). Get. Responses.Value("200").Value. @@ -42,7 +43,7 @@ func TestIssue341(t *testing.T) { Schema.Value. Type) - doc.InternalizeRefs(context.Background(), nil) + doc.InternalizeRefs(t.Context(), nil) bs, err = doc.MarshalJSON() require.NoError(t, err) require.JSONEq(t, `{ diff --git a/openapi3/issue344_test.go b/openapi3/issue344_test.go index 03fc1b22d..b4b6de204 100644 --- a/openapi3/issue344_test.go +++ b/openapi3/issue344_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue344(t *testing.T) { - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromFile("testdata/spec.yaml") @@ -16,5 +18,5 @@ func TestIssue344(t *testing.T) { err = doc.Validate(sl.Context) require.NoError(t, err) - require.Equal(t, &Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type) + require.Equal(t, &openapi3.Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type) } diff --git a/openapi3/issue376_test.go b/openapi3/issue376_test.go index 362f08d69..b39da454b 100644 --- a/openapi3/issue376_test.go +++ b/openapi3/issue376_test.go @@ -1,11 +1,12 @@ -package openapi3 +package openapi3_test import ( - "context" "fmt" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue376(t *testing.T) { @@ -28,7 +29,7 @@ info: version: 1.2.3.4 `) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -40,33 +41,33 @@ info: require.Equal(t, 2, len(doc.Components.Schemas)) require.Equal(t, 0, doc.Paths.Len()) - require.Equal(t, &Types{"string"}, doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type) + require.Equal(t, &openapi3.Types{"string"}, doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type) } func TestExclusiveValuesOfValuesAdditionalProperties(t *testing.T) { - schema := &Schema{ - AdditionalProperties: AdditionalProperties{ - Has: Ptr(false), - Schema: NewSchemaRef("", &Schema{}), + schema := &openapi3.Schema{ + AdditionalProperties: openapi3.AdditionalProperties{ + Has: openapi3.Ptr(false), + Schema: openapi3.NewSchemaRef("", &openapi3.Schema{}), }, } - err := schema.Validate(context.Background()) + err := schema.Validate(t.Context()) require.ErrorContains(t, err, ` to both `) - schema = &Schema{ - AdditionalProperties: AdditionalProperties{ - Has: Ptr(false), + schema = &openapi3.Schema{ + AdditionalProperties: openapi3.AdditionalProperties{ + Has: openapi3.Ptr(false), }, } - err = schema.Validate(context.Background()) + err = schema.Validate(t.Context()) require.NoError(t, err) - schema = &Schema{ - AdditionalProperties: AdditionalProperties{ - Schema: NewSchemaRef("", &Schema{}), + schema = &openapi3.Schema{ + AdditionalProperties: openapi3.AdditionalProperties{ + Schema: openapi3.NewSchemaRef("", &openapi3.Schema{}), }, } - err = schema.Validate(context.Background()) + err = schema.Validate(t.Context()) require.NoError(t, err) } @@ -120,7 +121,7 @@ info: for i, spec := range [][]byte{specJSON, specYAML} { t.Run(fmt.Sprintf("spec%02d", i), func(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) diff --git a/openapi3/issue382_test.go b/openapi3/issue382_test.go index c29b7e981..7db1454b4 100644 --- a/openapi3/issue382_test.go +++ b/openapi3/issue382_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestOverridingGlobalParametersValidation(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("testdata/Test_param_override.yml") require.NoError(t, err) err = doc.Validate(loader.Context) diff --git a/openapi3/issue495_test.go b/openapi3/issue495_test.go index b4d4a202f..ab2a96ab9 100644 --- a/openapi3/issue495_test.go +++ b/openapi3/issue495_test.go @@ -1,10 +1,12 @@ -package openapi3 +package openapi3_test import ( "os" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue495(t *testing.T) { @@ -37,7 +39,7 @@ paths: $ref: '#/components/schemas/schemaArray' `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData(spec) require.NoError(t, err) @@ -74,7 +76,7 @@ paths: $ref: '#/components/schemas/schemaArray' `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData(spec) require.NoError(t, err) @@ -82,7 +84,7 @@ paths: err = doc.Validate(sl.Context) require.NoError(t, err) - require.Equal(t, &Schema{Type: &Types{"object"}}, doc.Components.Schemas["schemaArray"].Value.Items.Value) + require.Equal(t, &openapi3.Schema{Type: &openapi3.Types{"object"}}, doc.Components.Schemas["schemaArray"].Value.Items.Value) } func TestIssue495WithDraft04(t *testing.T) { @@ -111,7 +113,7 @@ paths: $ref: http://json-schema.org/draft-04/schema `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true if os.Getenv("CI") == "true" { @@ -121,7 +123,10 @@ paths: doc, err := sl.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(sl.Context) + // draft-04 meta-schema contains $id and $schema; in OAS 3.0 these require + // opt-in via AllowExtraSiblingFields so the test can assert its real target + // (the unresolved inner "#" ref). + err = doc.Validate(sl.Context, openapi3.AllowExtraSiblingFields("$id", "$schema")) require.ErrorContains(t, err, `found unresolved ref: "#"`) } @@ -151,12 +156,15 @@ paths: $ref: testdata/draft04.yml `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() sl.IsExternalRefsAllowed = true doc, err := sl.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(sl.Context) + // draft-04 meta-schema contains $id and $schema; in OAS 3.0 these require + // opt-in via AllowExtraSiblingFields so the test can assert its real target + // (the unresolved inner "#" ref). + err = doc.Validate(sl.Context, openapi3.AllowExtraSiblingFields("$id", "$schema")) require.ErrorContains(t, err, `found unresolved ref: "#"`) } diff --git a/openapi3/issue499_test.go b/openapi3/issue499_test.go index dfbafa9dc..db83b2016 100644 --- a/openapi3/issue499_test.go +++ b/openapi3/issue499_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue499(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true _, err := loader.LoadFromFile("testdata/issue499/main.yml") require.NoError(t, err) diff --git a/openapi3/issue513_test.go b/openapi3/issue513_test.go index 38454f672..d26fb7667 100644 --- a/openapi3/issue513_test.go +++ b/openapi3/issue513_test.go @@ -2,13 +2,14 @@ package openapi3 import ( "encoding/json" + "strings" "testing" "github.com/stretchr/testify/require" ) func TestExtraSiblingsInRemoteRef(t *testing.T) { - spec := []byte(` + spec := ` openapi: 3.0.1 servers: - url: http://localhost:5000 @@ -31,7 +32,7 @@ paths: application/json: schema: $ref: http://schemas.sentex.io/store/categories.json -`[1:]) +` // When that site fails to respond: // see https://github.com/getkin/kin-openapi/issues/495 @@ -62,14 +63,19 @@ paths: // "maximum": 30 // } - sl := NewLoader() - sl.IsExternalRefsAllowed = true + for _, majmin := range []string{"'3.0'", "'3.1'"} { + t.Run(majmin, func(t *testing.T) { + t.Parallel() + sl := NewLoader() + sl.IsExternalRefsAllowed = true - doc, err := sl.LoadFromData(spec) - require.NoError(t, err) + doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.1", majmin))) + require.NoError(t, err) - err = doc.Validate(sl.Context, AllowExtraSiblingFields("$id", "$schema")) - require.NoError(t, err) + err = doc.Validate(sl.Context, AllowExtraSiblingFields("$id", "$schema")) + require.NoError(t, err) + }) + } } func TestIssue513OKWithExtension(t *testing.T) { @@ -104,14 +110,20 @@ components: description: A detailed message describing the error. type: string `[1:] - sl := NewLoader() - doc, err := sl.LoadFromData([]byte(spec)) - require.NoError(t, err) - err = doc.Validate(sl.Context) - require.NoError(t, err) - data, err := json.Marshal(doc) - require.NoError(t, err) - require.Contains(t, string(data), `x-my-extension`) + + for _, majmin := range []string{"3.0", "3.1"} { + t.Run(majmin, func(t *testing.T) { + t.Parallel() + sl := NewLoader() + doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) + require.NoError(t, err) + err = doc.Validate(sl.Context) + require.NoError(t, err) + data, err := json.Marshal(doc) + require.NoError(t, err) + require.Contains(t, string(data), `x-my-extension`) + }) + } } func TestIssue513KOHasExtraFieldSchema(t *testing.T) { @@ -149,12 +161,18 @@ components: description: A detailed message describing the error. type: string `[1:] - sl := NewLoader() - doc, err := sl.LoadFromData([]byte(spec)) - require.NoError(t, err) - require.Contains(t, doc.Paths.Value("/v1/operation").Delete.Responses.Default().Value.Extensions, `x-my-extension`) - err = doc.Validate(sl.Context) - require.ErrorContains(t, err, `extra sibling fields: [schema]`) + + for _, majmin := range []string{"3.0", "3.1"} { + t.Run(majmin, func(t *testing.T) { + t.Parallel() + sl := NewLoader() + doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) + require.NoError(t, err) + require.Contains(t, doc.Paths.Value("/v1/operation").Delete.Responses.Default().Value.Extensions, `x-my-extension`) + err = doc.Validate(sl.Context) + require.ErrorContains(t, err, `extra sibling fields: [schema]`) + }) + } } func TestIssue513KOMixesRefAlongWithOtherFieldsDisallowed(t *testing.T) { @@ -190,11 +208,17 @@ components: description: A detailed message describing the error. type: string `[1:] - sl := NewLoader() - doc, err := sl.LoadFromData([]byte(spec)) - require.NoError(t, err) - err = doc.Validate(sl.Context) - require.ErrorContains(t, err, `extra sibling fields: [description]`) + + for _, majmin := range []string{"3.0", "3.1"} { + t.Run(majmin, func(t *testing.T) { + t.Parallel() + sl := NewLoader() + doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) + require.NoError(t, err) + err = doc.Validate(sl.Context) + require.ErrorContains(t, err, `extra sibling fields: [description]`) + }) + } } func TestIssue513KOMixesRefAlongWithOtherFieldsAllowed(t *testing.T) { @@ -230,9 +254,15 @@ components: description: A detailed message describing the error. type: string `[1:] - sl := NewLoader() - doc, err := sl.LoadFromData([]byte(spec)) - require.NoError(t, err) - err = doc.Validate(sl.Context, AllowExtraSiblingFields("description")) - require.NoError(t, err) + + for _, majmin := range []string{"3.0", "3.1"} { + t.Run(majmin, func(t *testing.T) { + t.Parallel() + sl := NewLoader() + doc, err := sl.LoadFromData([]byte(strings.ReplaceAll(spec, "3.0.3", majmin))) + require.NoError(t, err) + err = doc.Validate(sl.Context, AllowExtraSiblingFields("description")) + require.NoError(t, err) + }) + } } diff --git a/openapi3/issue542_test.go b/openapi3/issue542_test.go index add8c7b43..8a0b264c4 100644 --- a/openapi3/issue542_test.go +++ b/openapi3/issue542_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue542(t *testing.T) { @@ -27,7 +29,7 @@ components: type: string `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData(spec) require.NoError(t, err) diff --git a/openapi3/issue570_test.go b/openapi3/issue570_test.go index 1575e5599..3548edb04 100644 --- a/openapi3/issue570_test.go +++ b/openapi3/issue570_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue570(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() _, err := loader.LoadFromFile("testdata/issue570.json") require.NoError(t, err) } diff --git a/openapi3/issue601_test.go b/openapi3/issue601_test.go index 09c98eeba..e867d87a8 100644 --- a/openapi3/issue601_test.go +++ b/openapi3/issue601_test.go @@ -17,11 +17,13 @@ func TestIssue601(t *testing.T) { doc, err := sl.LoadFromFile("testdata/lxkns.yaml") require.NoError(t, err) - err = doc.Validate(sl.Context) + // lxkns.yaml has `description` siblings alongside $ref (invalid in OAS 3.0). + // Allow them so we can exercise the example-type validation this test actually targets. + err = doc.Validate(sl.Context, AllowExtraSiblingFields("description")) require.ErrorContains(t, err, `invalid components: schema "DiscoveryResult": invalid example: Error at "/type": property "type" is missing`) require.ErrorContains(t, err, `| Error at "/nsid": property "nsid" is missing`) - err = doc.Validate(sl.Context, DisableExamplesValidation()) + err = doc.Validate(sl.Context, AllowExtraSiblingFields("description"), DisableExamplesValidation()) require.NoError(t, err) // Now let's remove all the invalid parts @@ -29,6 +31,6 @@ func TestIssue601(t *testing.T) { schema.Value.Example = nil } - err = doc.Validate(sl.Context) + err = doc.Validate(sl.Context, AllowExtraSiblingFields("description")) require.NoError(t, err) } diff --git a/openapi3/issue618_test.go b/openapi3/issue618_test.go index cd6758895..2e211e78e 100644 --- a/openapi3/issue618_test.go +++ b/openapi3/issue618_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue618(t *testing.T) { @@ -24,7 +26,7 @@ paths: $ref: ./testdata/schema618.yml#/components/schemas/JournalEntry `[1:] - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true ctx := loader.Context diff --git a/openapi3/issue638_test.go b/openapi3/issue638_test.go index 967195fd0..6fc227321 100644 --- a/openapi3/issue638_test.go +++ b/openapi3/issue638_test.go @@ -1,14 +1,16 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue638(t *testing.T) { - for i := 0; i < 50; i++ { - loader := NewLoader() + for range 50 { + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true // This path affects the occurrence of the issue #638. // ../openapi3/testdata/issue638/test1.yaml : reproduce @@ -16,6 +18,6 @@ func TestIssue638(t *testing.T) { // testdata/issue638/test1.yaml : reproduce doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml") require.NoError(t, err) - require.Equal(t, &Types{"int"}, doc.Components.Schemas["test1d"].Value.Type) + require.Equal(t, &openapi3.Types{"int"}, doc.Components.Schemas["test1d"].Value.Type) } } diff --git a/openapi3/issue697_test.go b/openapi3/issue697_test.go index c7317584a..859890935 100644 --- a/openapi3/issue697_test.go +++ b/openapi3/issue697_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue697(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("testdata/issue697.yml") require.NoError(t, err) err = doc.Validate(loader.Context) diff --git a/openapi3/issue741_test.go b/openapi3/issue741_test.go index aad522023..45b66fd5d 100644 --- a/openapi3/issue741_test.go +++ b/openapi3/issue741_test.go @@ -1,4 +1,4 @@ -package openapi3 +package openapi3_test import ( "fmt" @@ -8,36 +8,34 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue741(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) body := `{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Foo":{"type":"string"}}}}` - _, err := w.Write([]byte(body)) - if err != nil { + if _, err := w.Write([]byte(body)); err != nil { panic(err) } })) defer ts.Close() - rootSpec := []byte(fmt.Sprintf( + rootSpec := fmt.Appendf(nil, `{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"Bar1":{"$ref":"%s#/components/schemas/Foo"}}}}`, ts.URL, - )) + ) - wg := &sync.WaitGroup{} - n := 10 - for i := 0; i < n; i++ { - wg.Add(1) - go func() { - defer wg.Done() - loader := NewLoader() + var wg sync.WaitGroup + for range 10 { + wg.Go(func() { + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromData(rootSpec) require.NoError(t, err) require.NotNil(t, doc) - }() + }) } wg.Wait() } diff --git a/openapi3/issue753_test.go b/openapi3/issue753_test.go index 46c18f7aa..f18bd6e14 100644 --- a/openapi3/issue753_test.go +++ b/openapi3/issue753_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue753(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("testdata/issue753.yml") require.NoError(t, err) diff --git a/openapi3/issue759_test.go b/openapi3/issue759_test.go index 255d8b7b6..e2d523b82 100644 --- a/openapi3/issue759_test.go +++ b/openapi3/issue759_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue759(t *testing.T) { @@ -26,7 +28,7 @@ components: type: object `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.Nil(t, doc) diff --git a/openapi3/issue767_test.go b/openapi3/issue767_test.go index 04fd9d03f..e889c069e 100644 --- a/openapi3/issue767_test.go +++ b/openapi3/issue767_test.go @@ -11,7 +11,7 @@ import ( func TestIssue767(t *testing.T) { t.Parallel() - tests := [...]struct { + tests := []struct { name string schema *openapi3.Schema value map[string]any diff --git a/openapi3/issue794_test.go b/openapi3/issue794_test.go index 4958d29d7..2f95f4798 100644 --- a/openapi3/issue794_test.go +++ b/openapi3/issue794_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestCrashOnLoad(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("testdata/issue794.yml") require.NoError(t, err) err = doc.Validate(loader.Context) diff --git a/openapi3/issue796_test.go b/openapi3/issue796_test.go index 9c8be17f2..2f8d0ee7e 100644 --- a/openapi3/issue796_test.go +++ b/openapi3/issue796_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue796(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromFile("testdata/issue796.yml") require.NoError(t, err) diff --git a/openapi3/issue819_test.go b/openapi3/issue819_test.go index 121b50d98..ada613f9a 100644 --- a/openapi3/issue819_test.go +++ b/openapi3/issue819_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue819ResponsesGetPatternedFields(t *testing.T) { @@ -27,7 +29,7 @@ paths: type: object description: An error response body. `[1:] - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData([]byte(spec)) require.NoError(t, err) err = doc.Validate(sl.Context) diff --git a/openapi3/issue883_test.go b/openapi3/issue883_test.go index 7609e6ac3..1ff5dee5f 100644 --- a/openapi3/issue883_test.go +++ b/openapi3/issue883_test.go @@ -57,7 +57,7 @@ paths: require.YAMLEq(t, spec, string(marshalledYaml)) var newDoc openapi3.T - err = yaml.Unmarshal(marshalledYaml, &newDoc) + _, err = yaml.Unmarshal(marshalledYaml, &newDoc, yaml.DecodeOpts{DisableTimestamps: true}) require.NoError(t, err) require.NotNil(t, newDoc.Paths) require.Equal(t, doc, &newDoc) diff --git a/openapi3/issue927_test.go b/openapi3/issue927_test.go new file mode 100644 index 000000000..cb14f5452 --- /dev/null +++ b/openapi3/issue927_test.go @@ -0,0 +1,51 @@ +package openapi3_test + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestIssue927(t *testing.T) { + spec := ` +openapi: '3.0' +info: + title: title + version: 0.0.0 +components: + schemas: + NullableString: + type: string + nullable: true + NullableRef: + $ref: "#/components/schemas/String" + nullable: true + String: + type: string +` + + for _, openapi := range []string{"3.0", "3.1"} { + t.Run(openapi, func(t *testing.T) { + t.Parallel() + spec := strings.ReplaceAll(spec, "3.0", openapi) + + sl := openapi3.NewLoader() + doc, err := sl.LoadFromData([]byte(spec)) + require.NoError(t, err) + + err = doc.Validate(t.Context()) + if openapi == "3.0" { + require.ErrorContains(t, err, `invalid components: schema "NullableRef": extra sibling fields: [nullable]`) + t.SkipNow() + } + require.NoError(t, err) + + require.False(t, doc.Components.Schemas["String"].Value.Nullable) + require.True(t, doc.Components.Schemas["NullableString"].Value.Nullable) + require.True(t, doc.Components.Schemas["NullableRef"].Value.Nullable) + }) + } +} diff --git a/openapi3/issue961_test.go b/openapi3/issue961_test.go index 5dcbe9735..4eab2301d 100644 --- a/openapi3/issue961_test.go +++ b/openapi3/issue961_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue961(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true _, err := loader.LoadFromFile("./testdata/issue961/main.yml") require.NoError(t, err) diff --git a/openapi3/issue972_test.go b/openapi3/issue972_test.go index 5aaaae227..6bb274893 100644 --- a/openapi3/issue972_test.go +++ b/openapi3/issue972_test.go @@ -1,10 +1,12 @@ -package openapi3 +package openapi3_test import ( "testing" - "github.com/oasdiff/yaml3" + yaml "github.com/oasdiff/yaml3" "github.com/stretchr/testify/assert" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue972(t *testing.T) { @@ -47,7 +49,7 @@ info: }} { t.Logf("spec: %s", tc.spec) - loader := &Loader{} + loader := &openapi3.Loader{} doc, err := loader.LoadFromData([]byte(tc.spec)) assert.NoError(t, err) assert.NotNil(t, doc) diff --git a/openapi3/license.go b/openapi3/license.go index 56be06c1d..358b1784f 100644 --- a/openapi3/license.go +++ b/openapi3/license.go @@ -3,17 +3,22 @@ package openapi3 import ( "context" "encoding/json" - "errors" + "maps" ) // License is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object +// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#license-object type License struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` Name string `json:"name" yaml:"name"` // Required URL string `json:"url,omitempty" yaml:"url,omitempty"` + + // Identifier is an SPDX license expression for the API (OpenAPI 3.1) + // Either url or identifier can be specified, not both + Identifier string `json:"identifier,omitempty" yaml:"identifier,omitempty"` // OpenAPI >=3.1 } // MarshalJSON returns the JSON encoding of License. @@ -27,14 +32,15 @@ func (license License) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of License. func (license License) MarshalYAML() (any, error) { - m := make(map[string]any, 2+len(license.Extensions)) - for k, v := range license.Extensions { - m[k] = v - } + m := make(map[string]any, 3+len(license.Extensions)) + maps.Copy(m, license.Extensions) m["name"] = license.Name if x := license.URL; x != "" { m["url"] = x } + if x := license.Identifier; x != "" { + m["identifier"] = x + } return m, nil } @@ -48,6 +54,7 @@ func (license *License) UnmarshalJSON(data []byte) error { _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "name") delete(x.Extensions, "url") + delete(x.Extensions, "identifier") if len(x.Extensions) == 0 { x.Extensions = nil } @@ -58,10 +65,25 @@ func (license *License) UnmarshalJSON(data []byte) error { // Validate returns an error if License does not comply with the OpenAPI spec. func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) + + if license.Identifier != "" && !getValidationOptions(ctx).isOpenAPI31OrLater { + if err := me.emit(newLicenseIdentifierFieldFor31Plus(license.Origin)); err != nil { + return err + } + } if license.Name == "" { - return errors.New("value of license name must be a non-empty string") + if err := me.emit(newLicenseNameRequired(license.Origin)); err != nil { + return err + } + } + + if license.URL != "" && license.Identifier != "" { + if err := me.emit(newLicenseURLIdentifierExclusive(license.Origin)); err != nil { + return err + } } - return validateExtensions(ctx, license.Extensions) + return me.finalize(validateExtensions(ctx, license.Extensions, license.Origin)) } diff --git a/openapi3/link.go b/openapi3/link.go index e5e0c2944..60f78a029 100644 --- a/openapi3/link.go +++ b/openapi3/link.go @@ -3,8 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" - "fmt" + "maps" ) // Link is specified by OpenAPI/Swagger standard version 3. @@ -33,9 +32,7 @@ func (link Link) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Link. func (link Link) MarshalYAML() (any, error) { m := make(map[string]any, 6+len(link.Extensions)) - for k, v := range link.Extensions { - m[k] = v - } + maps.Copy(m, link.Extensions) if x := link.OperationRef; x != "" { m["operationRef"] = x @@ -86,13 +83,13 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error ctx = WithValidationOptions(ctx, opts...) if link.OperationID == "" && link.OperationRef == "" { - return errors.New("missing operationId or operationRef on link") + return newLinkOperationIDOrRefRequired(link.Origin) } if link.OperationID != "" && link.OperationRef != "" { - return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef) + return newLinkOperationIDRefExclusive(link.OperationID, link.OperationRef, link.Origin) } - return validateExtensions(ctx, link.Extensions) + return validateExtensions(ctx, link.Extensions, link.Origin) } // UnmarshalJSON sets Links to a copy of data. diff --git a/openapi3/loader.go b/openapi3/loader.go index f0a13e595..4f2697d1f 100644 --- a/openapi3/loader.go +++ b/openapi3/loader.go @@ -20,10 +20,6 @@ import ( // for backward compatibility but is not safe for concurrent use. var IncludeOrigin = false -func foundUnresolvedRef(ref string) error { - return fmt.Errorf("found unresolved ref: %q", ref) -} - func failedToResolveRefFragmentPart(value, what string) error { return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value) } @@ -41,6 +37,13 @@ type Loader struct { // ReadFromURIFunc allows overriding the any file/URL reading func ReadFromURIFunc ReadFromURIFunc + // JoinFunc allows overriding how relative $ref paths are resolved against + // a base path. When set, it is called instead of the default join logic + // that uses path.Dir and path.Join. This is useful when loading specs from + // non-filesystem sources (e.g. git objects, remote archives) where the base + // path follows a different convention than filesystem paths. + JoinFunc func(basePath *url.URL, relativePath *url.URL) *url.URL + Context context.Context rootDir string @@ -107,7 +110,7 @@ func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, el } } - resolvedPath, err := resolvePathWithRef(ref, rootPath) + resolvedPath, err := loader.resolvePathWithRef(ref, rootPath) if err != nil { return nil, err } @@ -270,10 +273,18 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { } } + for _, name := range componentNames(doc.Webhooks) { + if pathItem := doc.Webhooks[name]; pathItem != nil { + if err = loader.resolvePathItemRef(doc, pathItem, location); err != nil { + return + } + } + } + return } -func join(basePath *url.URL, relativePath *url.URL) *url.URL { +func defaultJoin(basePath *url.URL, relativePath *url.URL) *url.URL { if basePath == nil { return relativePath } @@ -282,24 +293,27 @@ func join(basePath *url.URL, relativePath *url.URL) *url.URL { return &newPath } -func resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL { +func (loader *Loader) resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL { if is_file(componentPath) { // support absolute paths if filepath.IsAbs(componentPath.Path) { return componentPath } - return join(basePath, componentPath) + if loader.JoinFunc != nil { + return loader.JoinFunc(basePath, componentPath) + } + return defaultJoin(basePath, componentPath) } return componentPath } -func resolvePathWithRef(ref string, rootPath *url.URL) (*url.URL, error) { +func (loader *Loader) resolvePathWithRef(ref string, rootPath *url.URL) (*url.URL, error) { parsedURL, err := url.Parse(ref) if err != nil { return nil, fmt.Errorf("cannot parse reference: %q: %w", ref, err) } - resolvedPath := resolvePath(rootPath, parsedURL) + resolvedPath := loader.resolvePath(rootPath, parsedURL) resolvedPath.Fragment = parsedURL.Fragment return resolvedPath, nil } @@ -324,7 +338,7 @@ func (loader *Loader) resolveRefPath(ref string, path *url.URL) (*url.URL, error } } - resolvedPath, err := resolvePathWithRef(ref, path) + resolvedPath, err := loader.resolvePathWithRef(ref, path) if err != nil { return nil, err } @@ -386,7 +400,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv } drill := func(cursor any) (any, error) { - for _, pathPart := range strings.Split(fragment[1:], "/") { + for pathPart := range strings.SplitSeq(fragment[1:], "/") { pathPart = unescapeRefString(pathPart) attempted := false @@ -548,7 +562,7 @@ func drillIntoField(cursor any, fieldName string) (any, error) { case reflect.Struct: hasFields := false - for i := 0; i < val.NumField(); i++ { + for i := range val.NumField() { hasFields = true if yamlTag := val.Type().Field(i).Tag.Get("yaml"); yamlTag != "-" { if tagName := strings.Split(yamlTag, ",")[0]; tagName != "" { @@ -671,8 +685,8 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat return err } } - for _, example := range value.Examples { - if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + for _, k := range componentNames(value.Examples) { + if err := loader.resolveExampleRef(doc, value.Examples[k], documentPath); err != nil { return err } } @@ -735,8 +749,8 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return err } } - for _, example := range contentType.Examples { - if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + for _, k := range componentNames(contentType.Examples) { + if err := loader.resolveExampleRef(doc, contentType.Examples[k], documentPath); err != nil { return err } } @@ -746,8 +760,8 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return err } } - for _, example := range value.Examples { - if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + for _, k := range componentNames(value.Examples) { + if err := loader.resolveExampleRef(doc, value.Examples[k], documentPath); err != nil { return err } } @@ -941,6 +955,18 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat component.setRefPath(resolved.RefPath()) } defer loader.unvisitRef(ref, component.Value) + + // OAS 3.1 / JSON Schema 2020-12: apply sibling keywords from the original schema + // object on top of the resolved $ref value. In 3.1, siblings are not ignored — + // they augment the referenced schema (e.g. deprecated:true alongside $ref). + // Only apply for OAS 3.1+ — in 3.0 $ref replaces its entire object and siblings + // are (validly) ignored. + if doc.IsOpenAPI31OrLater() && component.sibling != nil && component.Value != nil { + // Work on a copy so we don't mutate a schema shared by other references. + schemaCopy := *component.Value + applySiblingSchemaFields(&schemaCopy, component.sibling, component.extra) + component.Value = &schemaCopy + } } value := component.Value if value == nil { @@ -990,7 +1016,8 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat // Plain schema names like "Dog" or internal refs like "#/components/schemas/Dog" // don't need to be resolved by the loader. if value.Discriminator != nil { - for k, v := range value.Discriminator.Mapping { + for _, k := range componentNames(value.Discriminator.Mapping) { + v := value.Discriminator.Mapping[k] // Only resolve if it looks like an external ref (contains path separator) if strings.Contains(v.Ref, "/") && !strings.HasPrefix(v.Ref, "#") { if err := loader.resolveSchemaRef(doc, (*SchemaRef)(&v), documentPath, visited); err != nil { @@ -1000,6 +1027,72 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat } } } + + // OpenAPI 3.1 / JSON Schema 2020-12 fields + for _, v := range value.PrefixItems { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.Contains; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, name := range componentNames(value.PatternProperties) { + v := value.PatternProperties[name] + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, name := range componentNames(value.DependentSchemas) { + v := value.DependentSchemas[name] + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, name := range componentNames(value.Defs) { + v := value.Defs[name] + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.PropertyNames; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.UnevaluatedItems.Schema; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.UnevaluatedProperties.Schema; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.If; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.Then; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.Else; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.ContentSchema; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + return nil } @@ -1261,3 +1354,137 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat func unescapeRefString(ref string) string { return strings.ReplaceAll(strings.ReplaceAll(ref, "~1", "/"), "~0", "~") } + +// applySiblingSchemaFields overlays the fields listed in presentFields from sibling onto dst. +// It is used to honour keyword siblings of $ref in OpenAPI 3.1 / JSON Schema 2020-12, where +// sibling keywords are applied in addition to (not instead of) the referenced schema. +// Only fields that were explicitly present in the original YAML/JSON are applied; the presentFields +// slice (derived from SchemaRef.extra) carries this information. +func applySiblingSchemaFields(dst, sibling *Schema, presentFields []string) { + for _, field := range presentFields { + switch field { + case "oneOf": + dst.OneOf = sibling.OneOf + case "anyOf": + dst.AnyOf = sibling.AnyOf + case "allOf": + dst.AllOf = sibling.AllOf + case "not": + dst.Not = sibling.Not + case "type": + dst.Type = sibling.Type + case "title": + dst.Title = sibling.Title + case "format": + dst.Format = sibling.Format + case "description": + dst.Description = sibling.Description + case "enum": + dst.Enum = sibling.Enum + case "default": + dst.Default = sibling.Default + case "example": + dst.Example = sibling.Example + case "externalDocs": + dst.ExternalDocs = sibling.ExternalDocs + case "uniqueItems": + dst.UniqueItems = sibling.UniqueItems + case "exclusiveMinimum": + dst.ExclusiveMin = sibling.ExclusiveMin + case "exclusiveMaximum": + dst.ExclusiveMax = sibling.ExclusiveMax + case "nullable": + dst.Nullable = sibling.Nullable + case "readOnly": + dst.ReadOnly = sibling.ReadOnly + case "writeOnly": + dst.WriteOnly = sibling.WriteOnly + case "allowEmptyValue": + dst.AllowEmptyValue = sibling.AllowEmptyValue + case "deprecated": + dst.Deprecated = sibling.Deprecated + case "xml": + dst.XML = sibling.XML + case "minimum": + dst.Min = sibling.Min + case "maximum": + dst.Max = sibling.Max + case "multipleOf": + dst.MultipleOf = sibling.MultipleOf + case "minLength": + dst.MinLength = sibling.MinLength + case "maxLength": + dst.MaxLength = sibling.MaxLength + case "pattern": + dst.Pattern = sibling.Pattern + case "minItems": + dst.MinItems = sibling.MinItems + case "maxItems": + dst.MaxItems = sibling.MaxItems + case "items": + dst.Items = sibling.Items + case "required": + dst.Required = sibling.Required + case "properties": + dst.Properties = sibling.Properties + case "minProperties": + dst.MinProps = sibling.MinProps + case "maxProperties": + dst.MaxProps = sibling.MaxProps + case "additionalProperties": + dst.AdditionalProperties = sibling.AdditionalProperties + case "discriminator": + dst.Discriminator = sibling.Discriminator + case "const": + dst.Const = sibling.Const + case "examples": + dst.Examples = sibling.Examples + case "prefixItems": + dst.PrefixItems = sibling.PrefixItems + case "contains": + dst.Contains = sibling.Contains + case "minContains": + dst.MinContains = sibling.MinContains + case "maxContains": + dst.MaxContains = sibling.MaxContains + case "patternProperties": + dst.PatternProperties = sibling.PatternProperties + case "dependentSchemas": + dst.DependentSchemas = sibling.DependentSchemas + case "propertyNames": + dst.PropertyNames = sibling.PropertyNames + case "unevaluatedItems": + dst.UnevaluatedItems = sibling.UnevaluatedItems + case "unevaluatedProperties": + dst.UnevaluatedProperties = sibling.UnevaluatedProperties + case "if": + dst.If = sibling.If + case "then": + dst.Then = sibling.Then + case "else": + dst.Else = sibling.Else + case "dependentRequired": + dst.DependentRequired = sibling.DependentRequired + case "$defs": + dst.Defs = sibling.Defs + case "$schema": + dst.SchemaDialect = sibling.SchemaDialect + case "$comment": + dst.Comment = sibling.Comment + case "$id": + dst.SchemaID = sibling.SchemaID + case "$anchor": + dst.Anchor = sibling.Anchor + case "$dynamicRef": + dst.DynamicRef = sibling.DynamicRef + case "$dynamicAnchor": + dst.DynamicAnchor = sibling.DynamicAnchor + case "contentMediaType": + dst.ContentMediaType = sibling.ContentMediaType + case "contentEncoding": + dst.ContentEncoding = sibling.ContentEncoding + case "contentSchema": + dst.ContentSchema = sibling.ContentSchema + } + } +} diff --git a/openapi3/loader_31_conditional_test.go b/openapi3/loader_31_conditional_test.go new file mode 100644 index 000000000..29b89e950 --- /dev/null +++ b/openapi3/loader_31_conditional_test.go @@ -0,0 +1,38 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestResolveConditionalSchemaRefs(t *testing.T) { + loader := openapi3.NewLoader() + doc, err := loader.LoadFromFile("testdata/schema31_conditional.yml") + require.NoError(t, err) + + err = doc.Validate(loader.Context) + require.NoError(t, err) + + // Verify if/then/else refs are resolved + conditional := doc.Components.Schemas["ConditionalField"].Value + require.NotNil(t, conditional.If) + require.NotNil(t, conditional.If.Value) + require.True(t, conditional.If.Value.Type.Is("string")) + + require.NotNil(t, conditional.Then) + require.NotNil(t, conditional.Then.Value) + require.Equal(t, uint64(3), conditional.Then.Value.MinLength) + + require.NotNil(t, conditional.Else) + require.NotNil(t, conditional.Else.Value) + require.True(t, conditional.Else.Value.Type.Is("number")) + + // Verify dependentRequired is loaded + payment := doc.Components.Schemas["PaymentInfo"].Value + require.Equal(t, map[string][]string{ + "creditCard": {"billingAddress"}, + }, payment.DependentRequired) +} diff --git a/openapi3/loader_31_schema_refs_test.go b/openapi3/loader_31_schema_refs_test.go new file mode 100644 index 000000000..c720a2f23 --- /dev/null +++ b/openapi3/loader_31_schema_refs_test.go @@ -0,0 +1,176 @@ +package openapi3_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +// TestOAS31_RefSiblingKeyword verifies that sibling keywords alongside $ref are honoured +// when loading an OpenAPI 3.1 document. +// +// In OpenAPI 3.0 / JSON Schema draft-07, $ref replaces its entire object so any sibling +// keywords (e.g. deprecated, description) are silently ignored. +// In OpenAPI 3.1 / JSON Schema 2020-12, $ref and sibling keywords are both applied, so +// a property like: +// +// status: +// deprecated: true +// $ref: "#/components/schemas/PingStatus" +// +// should result in a SchemaRef whose Value has Deprecated==true. +func TestSchemaRefSiblingKeyword(t *testing.T) { + spec := ` +openapi: "3.1.0" +info: + title: Ref Sibling Test + version: "1.0" +paths: + /ping: + get: + operationId: getPing + responses: + "200": + description: ok + content: + application/json: + schema: + $ref: "#/components/schemas/PingResponse" +components: + schemas: + PingStatus: + type: string + enum: [ok, error] + PingResponse: + type: object + required: [message, status] + properties: + message: + type: string + status: + deprecated: true # sibling keyword alongside $ref — valid in OAS 3.1, ignored in 3.0 + $ref: "#/components/schemas/PingStatus" +` + + type testcase struct { + oas string + siblings, valid bool + } + + for _, tc := range []testcase{ + {oas: "3.1", siblings: true, valid: true}, {oas: "3.0"}, {oas: "3.0", valid: true}, + } { + t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) { + t.Parallel() + loader := openapi3.NewLoader() + + doc, err := loader.LoadFromData([]byte(strings.ReplaceAll(spec, "3.1.0", tc.oas))) + require.NoError(t, err) + + statusRef := doc.Components.Schemas["PingResponse"].Value.Properties["status"] + require.NotNil(t, statusRef) + + // The $ref should still be resolved. + require.Equal(t, statusRef.Ref, "#/components/schemas/PingStatus") + require.NotNil(t, statusRef.Value, "$ref to PingStatus should be resolved") + require.Equal(t, "string", statusRef.Value.Type.Slice()[0], "$ref target type should be string") + + require.Equal(t, tc.siblings, statusRef.Value.Deprecated, "deprecated:true sibling to $ref must be honoured in OAS 3.1") + + var valopts []openapi3.ValidationOption + if tc.valid && !tc.siblings { // For this test case let's try the option that allows siblings for 3.0 + valopts = append(valopts, openapi3.AllowExtraSiblingFields("deprecated")) + } + err = doc.Validate(loader.Context, valopts...) + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err, "Siblings to $ref is not valid OpenAPIv3.0 (by default)") + } + }) + } +} + +func TestResolveSchemaRefsIn31Fields(t *testing.T) { + loader := openapi3.NewLoader() + doc, err := loader.LoadFromFile("testdata/schema31refs.yml") + require.NoError(t, err) + + schemas := doc.Components.Schemas + + // prefixItems refs should be resolved + tupleArray := schemas["TupleArray"].Value + require.NotNil(t, tupleArray) + require.Len(t, tupleArray.PrefixItems, 2) + require.Equal(t, "#/components/schemas/StringType", tupleArray.PrefixItems[0].Ref) + require.NotNil(t, tupleArray.PrefixItems[0].Value, "prefixItems[0] $ref should be resolved") + require.Equal(t, "string", tupleArray.PrefixItems[0].Value.Type.Slice()[0]) + require.Equal(t, "#/components/schemas/IntegerType", tupleArray.PrefixItems[1].Ref) + require.NotNil(t, tupleArray.PrefixItems[1].Value, "prefixItems[1] $ref should be resolved") + require.Equal(t, "integer", tupleArray.PrefixItems[1].Value.Type.Slice()[0]) + + // contains ref should be resolved + arrayContains := schemas["ArrayWithContains"].Value + require.NotNil(t, arrayContains) + require.Equal(t, "#/components/schemas/StringType", arrayContains.Contains.Ref) + require.NotNil(t, arrayContains.Contains.Value, "contains $ref should be resolved") + require.Equal(t, "string", arrayContains.Contains.Value.Type.Slice()[0]) + + // patternProperties refs should be resolved + patternProps := schemas["ObjectWithPatternProperties"].Value + require.NotNil(t, patternProps) + pp := patternProps.PatternProperties["^x-"] + require.NotNil(t, pp) + require.Equal(t, "#/components/schemas/StringType", pp.Ref) + require.NotNil(t, pp.Value, "patternProperties $ref should be resolved") + + // dependentSchemas refs should be resolved + depSchemas := schemas["ObjectWithDependentSchemas"].Value + require.NotNil(t, depSchemas) + ds := depSchemas.DependentSchemas["name"] + require.NotNil(t, ds) + require.Equal(t, "#/components/schemas/NonNegative", ds.Ref) + require.NotNil(t, ds.Value, "dependentSchemas $ref should be resolved") + + // propertyNames ref should be resolved + propNames := schemas["ObjectWithPropertyNames"].Value + require.NotNil(t, propNames) + require.Equal(t, "#/components/schemas/NamePattern", propNames.PropertyNames.Ref) + require.NotNil(t, propNames.PropertyNames.Value, "propertyNames $ref should be resolved") + + // unevaluatedItems ref should be resolved + unItems := schemas["ArrayWithUnevaluatedItems"].Value + require.NotNil(t, unItems) + require.NotNil(t, unItems.UnevaluatedItems.Schema) + require.Equal(t, "#/components/schemas/StringType", unItems.UnevaluatedItems.Schema.Ref) + require.NotNil(t, unItems.UnevaluatedItems.Schema.Value, "unevaluatedItems $ref should be resolved") + + // unevaluatedProperties ref should be resolved + unProps := schemas["ObjectWithUnevaluatedProperties"].Value + require.NotNil(t, unProps) + require.NotNil(t, unProps.UnevaluatedProperties.Schema) + require.Equal(t, "#/components/schemas/StringType", unProps.UnevaluatedProperties.Schema.Ref) + require.NotNil(t, unProps.UnevaluatedProperties.Schema.Value, "unevaluatedProperties $ref should be resolved") + + // if/then/else refs should be resolved + ifThenElse := schemas["ObjectWithIfThenElse"].Value + require.NotNil(t, ifThenElse) + require.Equal(t, "#/components/schemas/StringType", ifThenElse.If.Ref) + require.NotNil(t, ifThenElse.If.Value, "if $ref should be resolved") + require.Equal(t, "string", ifThenElse.If.Value.Type.Slice()[0]) + require.Equal(t, "#/components/schemas/IntegerType", ifThenElse.Then.Ref) + require.NotNil(t, ifThenElse.Then.Value, "then $ref should be resolved") + require.Equal(t, "integer", ifThenElse.Then.Value.Type.Slice()[0]) + require.Equal(t, "#/components/schemas/NonNegative", ifThenElse.Else.Ref) + require.NotNil(t, ifThenElse.Else.Value, "else $ref should be resolved") + + // contentSchema ref should be resolved + contentSchema := schemas["StringWithContentSchema"].Value + require.NotNil(t, contentSchema) + require.Equal(t, "#/components/schemas/NonNegative", contentSchema.ContentSchema.Ref) + require.NotNil(t, contentSchema.ContentSchema.Value, "contentSchema $ref should be resolved") +} diff --git a/openapi3/loader_example_test.go b/openapi3/loader_example_test.go new file mode 100644 index 000000000..188af619e --- /dev/null +++ b/openapi3/loader_example_test.go @@ -0,0 +1,89 @@ +package openapi3_test + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +// ExampleLoader_JoinFunc demonstrates how to use JoinFunc to load a multi-file +// OpenAPI spec from a virtual path scheme (e.g. git refs like "rev:file.yaml"). +// +// When loading specs from non-filesystem sources via LoadFromDataWithPath and +// ReadFromURIFunc, the base path may use a custom prefix convention. The default +// path resolution uses path.Dir which does not understand such prefixes. JoinFunc +// lets the caller override path resolution to preserve the prefix. +func ExampleLoader_JoinFunc() { + // Set up test files in a temp directory. + dir, _ := os.MkdirTemp("", "joinfunc-example") + defer os.RemoveAll(dir) + + root := `openapi: "3.0.0" +info: + title: Pet API + version: "1.0" +paths: {} +components: + schemas: + Pet: + $ref: "./schemas/pet.yaml" +` + pet := `type: object +properties: + name: + type: string +` + os.MkdirAll(filepath.Join(dir, "schemas"), 0o755) + os.WriteFile(filepath.Join(dir, "root.yaml"), []byte(root), 0o644) + os.WriteFile(filepath.Join(dir, "schemas", "pet.yaml"), []byte(pet), 0o644) + + // Use a "rev:" prefix to simulate a virtual path scheme. + const prefix = "rev:" + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + // ReadFromURIFunc strips the prefix and reads from the real filesystem. + loader.ReadFromURIFunc = func(loader *openapi3.Loader, location *url.URL) ([]byte, error) { + p := location.Path + if strings.HasPrefix(p, prefix) { + p = p[len(prefix):] + } + return os.ReadFile(filepath.Join(dir, filepath.FromSlash(p))) + } + + // JoinFunc preserves the prefix when resolving relative $ref paths. + // Without this, path.Dir("rev:root.yaml") returns "." and $ref resolution breaks. + loader.JoinFunc = func(basePath *url.URL, relativePath *url.URL) *url.URL { + if basePath == nil { + return relativePath + } + result := *basePath + base := basePath.Path + if i := strings.IndexByte(base, ':'); i >= 0 { + pfx := base[:i+1] + filePart := base[i+1:] + result.Path = pfx + path.Join(path.Dir(filePart), relativePath.Path) + } else { + result.Path = path.Join(path.Dir(base), relativePath.Path) + } + return &result + } + + rootContent, _ := os.ReadFile(filepath.Join(dir, "root.yaml")) + doc, err := loader.LoadFromDataWithPath(rootContent, &url.URL{Path: prefix + "root.yaml"}) + if err != nil { + fmt.Println("error:", err) + return + } + + petSchema := doc.Components.Schemas["Pet"] + nameType := petSchema.Value.Properties["name"].Value.Type.Slice()[0] + fmt.Println("pet.name type:", nameType) + // Output: pet.name type: string +} diff --git a/openapi3/loader_http_error_test.go b/openapi3/loader_http_error_test.go index 715bcb75d..4c07c53e0 100644 --- a/openapi3/loader_http_error_test.go +++ b/openapi3/loader_http_error_test.go @@ -1,4 +1,4 @@ -package openapi3 +package openapi3_test import ( "fmt" @@ -8,6 +8,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestLoadReferenceFromRemoteURLFailsWithHttpError(t *testing.T) { @@ -42,7 +44,7 @@ func TestLoadReferenceFromRemoteURLFailsWithHttpError(t *testing.T) { } }`) - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true _, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"}) require.EqualError(t, err, fmt.Sprintf(`error resolving reference "%s/components.openapi.json#/components/headers/CustomTestHeader": error loading "%s/components.openapi.json": request returned status code 400`, ts.URL, ts.URL)) @@ -83,7 +85,7 @@ func TestLoadFromRemoteURLFailsWithHttpError(t *testing.T) { } }`) - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"}) diff --git a/openapi3/loader_issue220_test.go b/openapi3/loader_issue220_test.go index 1b0efd64a..b1cbd2d37 100644 --- a/openapi3/loader_issue220_test.go +++ b/openapi3/loader_issue220_test.go @@ -1,10 +1,12 @@ -package openapi3 +package openapi3_test import ( "path/filepath" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue220(t *testing.T) { @@ -14,7 +16,7 @@ func TestIssue220(t *testing.T) { } { t.Logf("specPath: %q", specPath) - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromFile(specPath) require.NoError(t, err) @@ -22,7 +24,7 @@ func TestIssue220(t *testing.T) { err = doc.Validate(loader.Context) require.NoError(t, err) - require.Equal(t, &Types{"integer"}, doc. + require.Equal(t, &openapi3.Types{"integer"}, doc. Paths.Value("/foo"). Get.Responses.Value("200").Value. Content["application/json"]. diff --git a/openapi3/loader_issue235_test.go b/openapi3/loader_issue235_test.go index 4cb54eff1..1d3916a9a 100644 --- a/openapi3/loader_issue235_test.go +++ b/openapi3/loader_issue235_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue235OK(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromFile("testdata/issue235.spec0.yml") require.NoError(t, err) @@ -17,7 +19,7 @@ func TestIssue235OK(t *testing.T) { func TestIssue235CircularDep(t *testing.T) { t.Skip("TODO: return an error on circular dependencies between external files of a spec") - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromFile("testdata/issue235.spec0-typo.yml") require.Nil(t, doc) diff --git a/openapi3/loader_outside_refs_test.go b/openapi3/loader_outside_refs_test.go index 7300c2c50..85124b697 100644 --- a/openapi3/loader_outside_refs_test.go +++ b/openapi3/loader_outside_refs_test.go @@ -1,13 +1,15 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestLoadOutsideRefs(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromFile("testdata/303bis/service.yaml") require.NoError(t, err) @@ -16,7 +18,7 @@ func TestLoadOutsideRefs(t *testing.T) { err = doc.Validate(loader.Context) require.NoError(t, err) - require.Equal(t, &Types{"string"}, doc. + require.Equal(t, &openapi3.Types{"string"}, doc. Paths.Value("/service"). Get. Responses.Value("200").Value. @@ -54,7 +56,12 @@ components: $ref: https://raw.githubusercontent.com/kubernetes/kubernetes/132f29769dfecfc808adc58f756be43171054094/api/openapi-spec/swagger.json#/definitions/io.k8s.api.rbac.v1.RoleList ` - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true - loader.LoadFromData([]byte(spec)) + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + require.NotNil(t, doc) + + err = doc.Validate(loader.Context, openapi3.AllowExtraSiblingFields("description")) + require.NoError(t, err) } diff --git a/openapi3/loader_paths_test.go b/openapi3/loader_paths_test.go index f7edc7374..5691197d9 100644 --- a/openapi3/loader_paths_test.go +++ b/openapi3/loader_paths_test.go @@ -1,10 +1,12 @@ -package openapi3 +package openapi3_test import ( "strings" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestPathsMustStartWithSlash(t *testing.T) { @@ -25,9 +27,10 @@ paths: "foo/bar": "invalid paths: path \"foo/bar\" does not start with a forward slash (/)", "/foo/bar": "", } { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData([]byte(strings.Replace(spec, "PATH", path, 1))) require.NoError(t, err) + err = doc.Validate(loader.Context) if expectedErr != "" { require.EqualError(t, err, expectedErr) diff --git a/openapi3/loader_read_from_uri_func_test.go b/openapi3/loader_read_from_uri_func_test.go index 50f3e38f5..7feb1287c 100644 --- a/openapi3/loader_read_from_uri_func_test.go +++ b/openapi3/loader_read_from_uri_func_test.go @@ -45,7 +45,8 @@ func TestLoaderReadFromURIFunc(t *testing.T) { doc, err := loader.LoadFromFile("recursiveRef/openapi.yml") require.NoError(t, err) require.NotNil(t, doc) - require.NoError(t, doc.Validate(loader.Context)) + err = doc.Validate(loader.Context) + require.NoError(t, err) require.Equal(t, "bar", doc. Paths.Value("/foo"). Get. diff --git a/openapi3/loader_relative_refs_test.go b/openapi3/loader_relative_refs_test.go index 05f9d0e4c..51a5db76c 100644 --- a/openapi3/loader_relative_refs_test.go +++ b/openapi3/loader_relative_refs_test.go @@ -198,7 +198,7 @@ func TestLoadFromDataWithExternalRef(t *testing.T) { for _, td := range refTestDataEntries { t.Logf("testcase %q", td.name) - spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json")) + spec := fmt.Appendf(nil, td.contentTemplate, "components.openapi.json") loader := NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"}) @@ -211,7 +211,7 @@ func TestLoadFromDataWithExternalRefResponseError(t *testing.T) { for _, td := range refTestDataEntriesResponseError { t.Logf("testcase %q", td.name) - spec := []byte(fmt.Sprintf(td.contentTemplate, "components.openapi.json")) + spec := fmt.Appendf(nil, td.contentTemplate, "components.openapi.json") loader := NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"}) @@ -224,7 +224,7 @@ func TestLoadFromDataWithExternalNestedRef(t *testing.T) { for _, td := range refTestDataEntries { t.Logf("testcase %q", td.name) - spec := []byte(fmt.Sprintf(td.contentTemplate, "nesteddir/nestedcomponents.openapi.json")) + spec := fmt.Appendf(nil, td.contentTemplate, "nesteddir/nestedcomponents.openapi.json") loader := NewLoader() loader.IsExternalRefsAllowed = true doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"}) diff --git a/openapi3/loader_test.go b/openapi3/loader_test.go index 67872d6d5..a01ae8cea 100644 --- a/openapi3/loader_test.go +++ b/openapi3/loader_test.go @@ -672,3 +672,51 @@ func TestReadFromIoReader_Nil(t *testing.T) { _, err := loader.LoadFromIoReader(nil) require.EqualError(t, err, "invalid reader: ") } + +func TestDefaultJoin(t *testing.T) { + tests := []struct { + name string + base string + rel string + expected string + }{ + { + name: "relative path", + base: "/home/user/openapi.yaml", + rel: "schemas/pet.yaml", + expected: "/home/user/schemas/pet.yaml", + }, + { + name: "dot-slash relative path", + base: "/home/user/openapi.yaml", + rel: "./schemas/pet.yaml", + expected: "/home/user/schemas/pet.yaml", + }, + { + name: "parent directory", + base: "/home/user/api/v1/openapi.yaml", + rel: "../common/types.yaml", + expected: "/home/user/api/common/types.yaml", + }, + { + name: "nil base returns relative", + base: "", + rel: "schemas/pet.yaml", + expected: "schemas/pet.yaml", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rel := &url.URL{Path: tt.rel} + if tt.base == "" { + result := defaultJoin(nil, rel) + require.Equal(t, tt.expected, result.Path) + return + } + base := &url.URL{Path: tt.base} + result := defaultJoin(base, rel) + require.Equal(t, tt.expected, result.Path) + }) + } +} diff --git a/openapi3/maplike.go b/openapi3/maplike.go index 5a26ae532..3a97be1c5 100644 --- a/openapi3/maplike.go +++ b/openapi3/maplike.go @@ -2,7 +2,7 @@ package openapi3 import ( "encoding/json" - "slices" + "maps" "strings" "github.com/go-openapi/jsonpointer" @@ -16,6 +16,11 @@ func NewResponsesWithCapacity(cap int) *Responses { return &Responses{m: make(map[string]*ResponseRef, cap)} } +// Keys returns the responses keys in a fixed order +func (responses *Responses) Keys() []string { + return componentNames(responses.Map()) +} + // Value returns the responses for key or nil func (responses *Responses) Value(key string) *ResponseRef { if responses.Len() == 0 { @@ -55,9 +60,7 @@ func (responses *Responses) Map() (m map[string]*ResponseRef) { return make(map[string]*ResponseRef) } m = make(map[string]*ResponseRef, len(responses.m)) - for k, v := range responses.m { - m[k] = v - } + maps.Copy(m, responses.m) return } @@ -81,11 +84,9 @@ func (responses *Responses) MarshalYAML() (any, error) { return nil, nil } m := make(map[string]any, responses.Len()+len(responses.Extensions)) - for k, v := range responses.Extensions { - m[k] = v - } - for k, v := range responses.Map() { - m[k] = v + maps.Copy(m, responses.Extensions) + for _, k := range responses.Keys() { + m[k] = responses.m[k] } return m, nil } @@ -106,18 +107,12 @@ func (responses *Responses) UnmarshalJSON(data []byte) (err error) { return } - ks := make([]string, 0, len(m)) - for k := range m { - ks = append(ks, k) - } - slices.Sort(ks) - x := Responses{ Extensions: make(map[string]any), m: make(map[string]*ResponseRef, len(m)), } - for _, k := range ks { + for _, k := range componentNames(m) { v := m[k] if strings.HasPrefix(k, "x-") { x.Extensions[k] = v @@ -146,6 +141,11 @@ func NewCallbackWithCapacity(cap int) *Callback { return &Callback{m: make(map[string]*PathItem, cap)} } +// Keys returns the callback keys in a fixed order +func (callback *Callback) Keys() []string { + return componentNames(callback.Map()) +} + // Value returns the callback for key or nil func (callback *Callback) Value(key string) *PathItem { if callback.Len() == 0 { @@ -185,9 +185,7 @@ func (callback *Callback) Map() (m map[string]*PathItem) { return make(map[string]*PathItem) } m = make(map[string]*PathItem, len(callback.m)) - for k, v := range callback.m { - m[k] = v - } + maps.Copy(m, callback.m) return } @@ -211,11 +209,9 @@ func (callback *Callback) MarshalYAML() (any, error) { return nil, nil } m := make(map[string]any, callback.Len()+len(callback.Extensions)) - for k, v := range callback.Extensions { - m[k] = v - } - for k, v := range callback.Map() { - m[k] = v + maps.Copy(m, callback.Extensions) + for _, k := range callback.Keys() { + m[k] = callback.m[k] } return m, nil } @@ -236,18 +232,12 @@ func (callback *Callback) UnmarshalJSON(data []byte) (err error) { return } - ks := make([]string, 0, len(m)) - for k := range m { - ks = append(ks, k) - } - slices.Sort(ks) - x := Callback{ Extensions: make(map[string]any), m: make(map[string]*PathItem, len(m)), } - for _, k := range ks { + for _, k := range componentNames(m) { v := m[k] if strings.HasPrefix(k, "x-") { x.Extensions[k] = v @@ -276,6 +266,11 @@ func NewPathsWithCapacity(cap int) *Paths { return &Paths{m: make(map[string]*PathItem, cap)} } +// Keys returns the paths keys in a fixed order +func (paths *Paths) Keys() []string { + return componentNames(paths.Map()) +} + // Value returns the paths for key or nil func (paths *Paths) Value(key string) *PathItem { if paths.Len() == 0 { @@ -315,9 +310,7 @@ func (paths *Paths) Map() (m map[string]*PathItem) { return make(map[string]*PathItem) } m = make(map[string]*PathItem, len(paths.m)) - for k, v := range paths.m { - m[k] = v - } + maps.Copy(m, paths.m) return } @@ -341,11 +334,9 @@ func (paths *Paths) MarshalYAML() (any, error) { return nil, nil } m := make(map[string]any, paths.Len()+len(paths.Extensions)) - for k, v := range paths.Extensions { - m[k] = v - } - for k, v := range paths.Map() { - m[k] = v + maps.Copy(m, paths.Extensions) + for _, k := range paths.Keys() { + m[k] = paths.m[k] } return m, nil } @@ -366,18 +357,12 @@ func (paths *Paths) UnmarshalJSON(data []byte) (err error) { return } - ks := make([]string, 0, len(m)) - for k := range m { - ks = append(ks, k) - } - slices.Sort(ks) - x := Paths{ Extensions: make(map[string]any), m: make(map[string]*PathItem, len(m)), } - for _, k := range ks { + for _, k := range componentNames(m) { v := m[k] if strings.HasPrefix(k, "x-") { x.Extensions[k] = v diff --git a/openapi3/maplike_test.go b/openapi3/maplike_test.go index fa85ed827..e7efe1f55 100644 --- a/openapi3/maplike_test.go +++ b/openapi3/maplike_test.go @@ -1,91 +1,93 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestMaplikeMethods(t *testing.T) { t.Parallel() - t.Run("*Responses", func(t *testing.T) { + t.Run("*openapi3.Responses", func(t *testing.T) { t.Parallel() t.Run("nil", func(t *testing.T) { - x := (*Responses)(nil) + x := (*openapi3.Responses)(nil) require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*ResponseRef{}, x.Map()) - require.Equal(t, (*ResponseRef)(nil), x.Value("key")) - require.Panics(t, func() { x.Set("key", &ResponseRef{}) }) + require.Equal(t, map[string]*openapi3.ResponseRef{}, x.Map()) + require.Equal(t, (*openapi3.ResponseRef)(nil), x.Value("key")) + require.Panics(t, func() { x.Set("key", &openapi3.ResponseRef{}) }) require.NotPanics(t, func() { x.Delete("key") }) }) t.Run("nonnil", func(t *testing.T) { - x := &Responses{} + x := &openapi3.Responses{} require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*ResponseRef{}, x.Map()) - require.Equal(t, (*ResponseRef)(nil), x.Value("key")) - x.Set("key", &ResponseRef{}) + require.Equal(t, map[string]*openapi3.ResponseRef{}, x.Map()) + require.Equal(t, (*openapi3.ResponseRef)(nil), x.Value("key")) + x.Set("key", &openapi3.ResponseRef{}) require.Equal(t, 1, x.Len()) - require.Equal(t, map[string]*ResponseRef{"key": {}}, x.Map()) - require.Equal(t, &ResponseRef{}, x.Value("key")) + require.Equal(t, map[string]*openapi3.ResponseRef{"key": {}}, x.Map()) + require.Equal(t, &openapi3.ResponseRef{}, x.Value("key")) x.Delete("key") require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*ResponseRef{}, x.Map()) - require.Equal(t, (*ResponseRef)(nil), x.Value("key")) + require.Equal(t, map[string]*openapi3.ResponseRef{}, x.Map()) + require.Equal(t, (*openapi3.ResponseRef)(nil), x.Value("key")) require.NotPanics(t, func() { x.Delete("key") }) }) }) - t.Run("*Callback", func(t *testing.T) { + t.Run("*openapi3.Callback", func(t *testing.T) { t.Parallel() t.Run("nil", func(t *testing.T) { - x := (*Callback)(nil) + x := (*openapi3.Callback)(nil) require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) - require.Panics(t, func() { x.Set("key", &PathItem{}) }) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) + require.Panics(t, func() { x.Set("key", &openapi3.PathItem{}) }) require.NotPanics(t, func() { x.Delete("key") }) }) t.Run("nonnil", func(t *testing.T) { - x := &Callback{} + x := &openapi3.Callback{} require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) - x.Set("key", &PathItem{}) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) + x.Set("key", &openapi3.PathItem{}) require.Equal(t, 1, x.Len()) - require.Equal(t, map[string]*PathItem{"key": {}}, x.Map()) - require.Equal(t, &PathItem{}, x.Value("key")) + require.Equal(t, map[string]*openapi3.PathItem{"key": {}}, x.Map()) + require.Equal(t, &openapi3.PathItem{}, x.Value("key")) x.Delete("key") require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) require.NotPanics(t, func() { x.Delete("key") }) }) }) - t.Run("*Paths", func(t *testing.T) { + t.Run("*openapi3.Paths", func(t *testing.T) { t.Parallel() t.Run("nil", func(t *testing.T) { - x := (*Paths)(nil) + x := (*openapi3.Paths)(nil) require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) - require.Panics(t, func() { x.Set("key", &PathItem{}) }) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) + require.Panics(t, func() { x.Set("key", &openapi3.PathItem{}) }) require.NotPanics(t, func() { x.Delete("key") }) }) t.Run("nonnil", func(t *testing.T) { - x := &Paths{} + x := &openapi3.Paths{} require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) - x.Set("key", &PathItem{}) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) + x.Set("key", &openapi3.PathItem{}) require.Equal(t, 1, x.Len()) - require.Equal(t, map[string]*PathItem{"key": {}}, x.Map()) - require.Equal(t, &PathItem{}, x.Value("key")) + require.Equal(t, map[string]*openapi3.PathItem{"key": {}}, x.Map()) + require.Equal(t, &openapi3.PathItem{}, x.Value("key")) x.Delete("key") require.Equal(t, 0, x.Len()) - require.Equal(t, map[string]*PathItem{}, x.Map()) - require.Equal(t, (*PathItem)(nil), x.Value("key")) + require.Equal(t, map[string]*openapi3.PathItem{}, x.Map()) + require.Equal(t, (*openapi3.PathItem)(nil), x.Value("key")) require.NotPanics(t, func() { x.Delete("key") }) }) }) diff --git a/openapi3/mapping_test.go b/openapi3/mapping_test.go index 0c1c1995e..11ac15330 100644 --- a/openapi3/mapping_test.go +++ b/openapi3/mapping_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestMapping(t *testing.T) { @@ -51,7 +53,7 @@ components: lovesRocks: type: boolean ` - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true _, err := loader.LoadFromData([]byte(schema)) require.NoError(t, err) diff --git a/openapi3/marsh.go b/openapi3/marsh.go index a54907074..6483fa9a4 100644 --- a/openapi3/marsh.go +++ b/openapi3/marsh.go @@ -30,7 +30,10 @@ func unmarshal(data []byte, v any, includeOrigin bool, location *url.URL) error if location != nil { file = location.String() } - if tree, err := yaml.UnmarshalWithOriginTree(data, v, yaml.OriginOpt{Enabled: includeOrigin, File: file}); err == nil { + if tree, err := yaml.Unmarshal(data, v, yaml.DecodeOpts{ + Origin: yaml.OriginOpt{Enabled: includeOrigin, File: file}, + DisableTimestamps: true, + }); err == nil { applyOrigins(v, tree) return nil } else { diff --git a/openapi3/marsh_test.go b/openapi3/marsh_test.go index 4ddc4fa93..cf0eb4d28 100644 --- a/openapi3/marsh_test.go +++ b/openapi3/marsh_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestUnmarshalError(t *testing.T) { @@ -35,7 +37,7 @@ paths: $ref: '#/components/schemas/schemaArray' # <- Should have been a list `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() _, err := sl.LoadFromData(spec) require.ErrorContains(t, err, `json: cannot unmarshal object into field Schema.allOf of type openapi3.SchemaRefs`) @@ -68,7 +70,7 @@ paths: - $ref: '#/components/schemas/schemaArray' # <- `[1:]) - sl := NewLoader() + sl := openapi3.NewLoader() doc, err := sl.LoadFromData(spec) require.NoError(t, err) diff --git a/openapi3/media_type.go b/openapi3/media_type.go index 6373a7b65..c57158959 100644 --- a/openapi3/media_type.go +++ b/openapi3/media_type.go @@ -3,9 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" - "fmt" - "slices" + "maps" "github.com/go-openapi/jsonpointer" ) @@ -76,9 +74,7 @@ func (mediaType MediaType) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of MediaType. func (mediaType MediaType) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(mediaType.Extensions)) - for k, v := range mediaType.Extensions { - m[k] = v - } + maps.Copy(m, mediaType.Extensions) if x := mediaType.Schema; x != nil { m["schema"] = x } @@ -109,6 +105,7 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { if len(x.Extensions) == 0 { x.Extensions = nil } + delete(x.Encoding, originKey) *mediaType = MediaType(x) return nil } @@ -126,36 +123,33 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti } if mediaType.Example != nil && mediaType.Examples != nil { - return errors.New("example and examples are mutually exclusive") + return newMediaTypeExampleExamplesExclusive(mediaType.Origin) } if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { if example := mediaType.Example; example != nil { if err := validateExampleValue(ctx, example, schema.Value); err != nil { - return fmt.Errorf("invalid example: %w", err) + return newSchemaValueError("example", err, mediaType.Origin) } } if examples := mediaType.Examples; examples != nil { - names := make([]string, 0, len(examples)) - for name := range examples { - names = append(names, name) - } - slices.Sort(names) - for _, k := range names { + for _, k := range componentNames(examples) { v := examples[k] if err := v.Validate(ctx); err != nil { - return fmt.Errorf("example %s: %w", k, err) + return &MediaTypeExampleValidationError{ExampleName: k, Cause: err} } if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { - return fmt.Errorf("example %s: %w", k, err) + return newSchemaValueError("example", + &MediaTypeExampleValidationError{ExampleName: k, Cause: err}, + exampleValueOrigin(v.Value, mediaType.Origin)) } } } } } - return validateExtensions(ctx, mediaType.Extensions) + return validateExtensions(ctx, mediaType.Extensions, mediaType.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable diff --git a/openapi3/media_type_test.go b/openapi3/media_type_test.go index 099c4b667..c1d4609c1 100644 --- a/openapi3/media_type_test.go +++ b/openapi3/media_type_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "encoding/json" "testing" @@ -15,17 +14,17 @@ func TestMediaTypeJSON(t *testing.T) { require.NotEmpty(t, data) t.Log("Unmarshal *openapi3.MediaType from JSON") - docA := &MediaType{} - err = json.Unmarshal(mediaTypeJSON, &docA) + mt := &MediaType{} + err = json.Unmarshal(mediaTypeJSON, &mt) require.NoError(t, err) require.NotEmpty(t, data) t.Log("Validate *openapi3.MediaType") - err = docA.Validate(context.Background()) + err = mt.Validate(t.Context()) require.NoError(t, err) t.Log("Ensure representations match") - dataA, err := json.Marshal(docA) + dataA, err := json.Marshal(mt) require.NoError(t, err) require.JSONEq(t, string(data), string(mediaTypeJSON)) require.JSONEq(t, string(data), string(dataA)) diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go index ed8a016c5..267064ff8 100644 --- a/openapi3/openapi3.go +++ b/openapi3/openapi3.go @@ -3,26 +3,31 @@ package openapi3 import ( "context" "encoding/json" - "errors" "fmt" + "maps" "net/url" + "slices" "github.com/go-openapi/jsonpointer" ) // T is the root of an OpenAPI v3 document // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object +// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object type T struct { Extensions map[string]any `json:"-" yaml:"-"` - - OpenAPI string `json:"openapi" yaml:"openapi"` // Required - Components *Components `json:"components,omitempty" yaml:"components,omitempty"` - Info *Info `json:"info" yaml:"info"` // Required - Paths *Paths `json:"paths" yaml:"paths"` // Required - Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` - Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` - Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Origin *Origin `json:"-" yaml:"-"` + + OpenAPI string `json:"openapi" yaml:"openapi"` // Required + Components *Components `json:"components,omitempty" yaml:"components,omitempty"` + Info *Info `json:"info" yaml:"info"` // Required + Paths *Paths `json:"paths" yaml:"paths"` // Required in 3.0, optional in 3.1 + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Webhooks map[string]*PathItem `json:"webhooks,omitempty" yaml:"webhooks,omitempty"` // OpenAPI >=3.1 + JSONSchemaDialect string `json:"jsonSchemaDialect,omitempty" yaml:"jsonSchemaDialect,omitempty"` // OpenAPI >=3.1 visited visitedComponent url *url.URL @@ -34,6 +39,46 @@ type T struct { integerFormats map[string]IntegerFormatValidator } +// IsOpenAPI30 returns whether doc is an OpenAPI document version 3.0.x. +// Returns true for 3, 3.0, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, ... +// And false for 3.1.0, 3.2, ... and for invalid strings. +func (doc *T) IsOpenAPI30() bool { + return doc.OpenAPIMajorMinor() == "3.0" +} + +// IsOpenAPI31OrLater returns whether doc is an OpenAPI document version >=3.1. +// Returns true for 3.1, 3.1.0, 3.1.1, 3.1.2, 3.2.0, ... +// And false for cases where IsOpenAPI30 returns true and for invalid strings. +func (doc *T) IsOpenAPI31OrLater() bool { + return slices.Contains([]string{"3.1", "3.2"}, doc.OpenAPIMajorMinor()) +} + +func errFieldFor31Plus(field string, origin *Origin) error { + return newFieldFor31Plus(field, origin) +} + +func errValueOfFieldFor31Plus(value any, field string) error { + return fmt.Errorf("value %q of field %s is for OpenAPI >=3.1", value, field) +} + +// OpenAPIMajorMinor returns 3.y of the OpenAPI "3.y" or "3.y.z" version of the document. +// Returns the empty string for invalid OpenAPI version strings. +func (doc *T) OpenAPIMajorMinor() string { + if doc == nil { + return "" + } + switch doc.OpenAPI { + case "3", "3.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4": + return "3.0" + case "3.1", "3.1.0", "3.1.1", "3.1.2": + return "3.1" + case "3.2", "3.2.0": + return "3.2" + default: + return "" + } +} + var _ jsonpointer.JSONPointable = (*T)(nil) // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -55,6 +100,10 @@ func (doc *T) JSONLookup(token string) (any, error) { return doc.Tags, nil case "externalDocs": return doc.ExternalDocs, nil + case "webhooks": + return doc.Webhooks, nil + case "jsonSchemaDialect": + return doc.JSONSchemaDialect, nil } v, _, err := jsonpointer.GetForToken(doc.Extensions, token) @@ -75,10 +124,8 @@ func (doc *T) MarshalYAML() (any, error) { if doc == nil { return nil, nil } - m := make(map[string]any, 4+len(doc.Extensions)) - for k, v := range doc.Extensions { - m[k] = v - } + m := make(map[string]any, 10+len(doc.Extensions)) + maps.Copy(m, doc.Extensions) m["openapi"] = doc.OpenAPI if x := doc.Components; x != nil { m["components"] = x @@ -97,6 +144,12 @@ func (doc *T) MarshalYAML() (any, error) { if x := doc.ExternalDocs; x != nil { m["externalDocs"] = x } + if x := doc.Webhooks; len(x) != 0 { + m["webhooks"] = x + } + if x := doc.JSONSchemaDialect; x != "" { + m["jsonSchemaDialect"] = x + } return m, nil } @@ -116,9 +169,12 @@ func (doc *T) UnmarshalJSON(data []byte) error { delete(x.Extensions, "servers") delete(x.Extensions, "tags") delete(x.Extensions, "externalDocs") + delete(x.Extensions, "webhooks") + delete(x.Extensions, "jsonSchemaDialect") if len(x.Extensions) == 0 { x.Extensions = nil } + delete(x.Webhooks, originKey) *doc = T(x) return nil } @@ -203,69 +259,127 @@ func (doc *T) GetSchemaValidationOptions() []SchemaValidationOption { // Validate returns an error if T does not comply with the OpenAPI spec. // Validations Options can be provided to modify the validation behavior. +// +// By default, doc.OpenAPI's field dictates whether "JSON Schema Draft 2020-12" validation +// is enabled. func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error { + if doc.IsOpenAPI31OrLater() { + opts = append(opts, IsOpenAPI31OrLater()) + } ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if doc.OpenAPI == "" { - return errors.New("value of openapi must be a non-empty string") + if err := me.emit(newOpenAPIVersionRequired(doc.Origin)); err != nil { + return err + } + } + + if doc.Webhooks != nil && !doc.IsOpenAPI31OrLater() { + if err := me.emit(newWebhooksFieldFor31Plus(doc.Origin)); err != nil { + return err + } + } + if doc.JSONSchemaDialect != "" && !doc.IsOpenAPI31OrLater() { + if err := me.emit(newJSONSchemaDialectFieldFor31Plus(doc.Origin)); err != nil { + return err + } + } + + wrapSection := func(section string) func(error) error { + return func(e error) error { return &SectionValidationError{Section: section, Cause: e} } } var wrap func(error) error - wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) } + wrap = wrapSection("components") if v := doc.Components; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) } + wrap = wrapSection("info") if v := doc.Info; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } - } else { - return wrap(errors.New("must be an object")) + } else if err := me.emit(wrap(newInfoRequired(doc.Origin))); err != nil { + return err } - wrap = func(e error) error { return fmt.Errorf("invalid paths: %w", e) } + wrap = wrapSection("paths") if v := doc.Paths; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err + } + } else if doc.IsOpenAPI30() { + if err := me.emit(wrap(newPathsRequired(doc.Origin))); err != nil { + return err } - } else { - return wrap(errors.New("must be an object")) } - wrap = func(e error) error { return fmt.Errorf("invalid security: %w", e) } + wrap = wrapSection("security") if v := doc.Security; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - wrap = func(e error) error { return fmt.Errorf("invalid servers: %w", e) } + wrap = wrapSection("servers") if v := doc.Servers; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - wrap = func(e error) error { return fmt.Errorf("invalid tags: %w", e) } + wrap = wrapSection("tags") if v := doc.Tags; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - wrap = func(e error) error { return fmt.Errorf("invalid external docs: %w", e) } + wrap = wrapSection("external docs") if v := doc.ExternalDocs; v != nil { - if err := v.Validate(ctx); err != nil { - return wrap(err) + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err + } + } + + wrap = wrapSection("webhooks") + for _, name := range componentNames(doc.Webhooks) { + pathItem := doc.Webhooks[name] + if pathItem == nil { + if err := me.emit(wrap(newWebhookNil(name))); err != nil { + return err + } + // Nothing to descend into for a nil webhook; the nil itself + // is the only finding under this name until the entry is + // populated, so continue to the next webhook. + continue + } + wrapWebhook := func(e error) error { return wrap(&WebhookValidationError{Name: name, Cause: e}) } + if err := me.emitWrapped(wrapWebhook, pathItem.Validate(ctx)); err != nil { + return err + } + } + + wrap = wrapSection("jsonSchemaDialect") + if doc.JSONSchemaDialect != "" { + u, err := url.Parse(doc.JSONSchemaDialect) + if err != nil { + if err = me.emit(wrap(err)); err != nil { + return err + } + } else if u.Scheme == "" { + if err := me.emit(wrap(newJSONSchemaDialectAbsoluteURIRequired(doc.Origin))); err != nil { + return err + } } } - return validateExtensions(ctx, doc.Extensions) + return me.finalize(validateExtensions(ctx, doc.Extensions, doc.Origin)) } // ValidateSchemaJSON validates data against a schema using this document's format validators. diff --git a/openapi3/openapi3_test.go b/openapi3/openapi3_test.go index bb42df569..3df69cb51 100644 --- a/openapi3/openapi3_test.go +++ b/openapi3/openapi3_test.go @@ -1,18 +1,20 @@ -package openapi3 +package openapi3_test import ( - "context" "encoding/json" + "fmt" "strings" "testing" "github.com/oasdiff/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestRefsJSON(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() t.Log("Marshal *T to JSON") data, err := json.Marshal(spec()) @@ -20,7 +22,7 @@ func TestRefsJSON(t *testing.T) { require.NotEmpty(t, data) t.Log("Unmarshal *T from JSON") - docA := &T{} + docA := &openapi3.T{} err = json.Unmarshal(specJSON, &docA) require.NoError(t, err) require.NotEmpty(t, data) @@ -50,7 +52,7 @@ func TestRefsJSON(t *testing.T) { } func TestRefsYAML(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() t.Log("Marshal *T to YAML") data, err := yaml.Marshal(spec()) @@ -58,8 +60,8 @@ func TestRefsYAML(t *testing.T) { require.NotEmpty(t, data) t.Log("Unmarshal *T from YAML") - docA := &T{} - err = yaml.Unmarshal(specYAML, &docA) + docA := &openapi3.T{} + _, err = yaml.Unmarshal(specYAML, &docA, yaml.DecodeOpts{DisableTimestamps: true}) require.NoError(t, err) require.NotEmpty(t, data) @@ -236,54 +238,54 @@ var specJSON = []byte(` } `) -func spec() *T { - parameter := &Parameter{ +func spec() *openapi3.T { + parameter := &openapi3.Parameter{ Description: "Some parameter", Name: "example", In: "query", - Schema: &SchemaRef{ + Schema: &openapi3.SchemaRef{ Ref: "#/components/schemas/someSchema", }, } - requestBody := &RequestBody{ + requestBody := &openapi3.RequestBody{ Description: "Some request body", - Content: NewContent(), + Content: openapi3.NewContent(), } responseDescription := "Some response" - response := &Response{ + response := &openapi3.Response{ Description: &responseDescription, } - schema := &Schema{ + schema := &openapi3.Schema{ Description: "Some schema", } example := map[string]string{"name": "Some example"} - return &T{ + return &openapi3.T{ OpenAPI: "3.0", - Info: &Info{ + Info: &openapi3.Info{ Title: "MyAPI", Version: "0.1", }, - Paths: NewPaths( - WithPath("/hello", &PathItem{ - Post: &Operation{ - Parameters: Parameters{ + Paths: openapi3.NewPaths( + openapi3.WithPath("/hello", &openapi3.PathItem{ + Post: &openapi3.Operation{ + Parameters: openapi3.Parameters{ { Ref: "#/components/parameters/someParameter", Value: parameter, }, }, - RequestBody: &RequestBodyRef{ + RequestBody: &openapi3.RequestBodyRef{ Ref: "#/components/requestBodies/someRequestBody", Value: requestBody, }, - Responses: NewResponses( - WithStatus(200, &ResponseRef{ + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ Ref: "#/components/responses/someResponse", Value: response, }), ), }, - Parameters: Parameters{ + Parameters: openapi3.Parameters{ { Ref: "#/components/parameters/someParameter", Value: parameter, @@ -291,31 +293,31 @@ func spec() *T { }, }), ), - Components: &Components{ - Parameters: ParametersMap{ + Components: &openapi3.Components{ + Parameters: openapi3.ParametersMap{ "someParameter": {Value: parameter}, }, - RequestBodies: RequestBodies{ + RequestBodies: openapi3.RequestBodies{ "someRequestBody": {Value: requestBody}, }, - Responses: ResponseBodies{ + Responses: openapi3.ResponseBodies{ "someResponse": {Value: response}, }, - Schemas: Schemas{ + Schemas: openapi3.Schemas{ "someSchema": {Value: schema}, }, - Headers: Headers{ + Headers: openapi3.Headers{ "someHeader": {Ref: "#/components/headers/otherHeader"}, - "otherHeader": {Value: &Header{Parameter{Schema: &SchemaRef{Value: NewStringSchema()}}}}, + "otherHeader": {Value: &openapi3.Header{openapi3.Parameter{Schema: &openapi3.SchemaRef{Value: openapi3.NewStringSchema()}}}}, }, - Examples: Examples{ + Examples: openapi3.Examples{ "someExample": {Ref: "#/components/examples/otherExample"}, - "otherExample": {Value: NewExample(example)}, + "otherExample": {Value: openapi3.NewExample(example)}, }, - SecuritySchemes: SecuritySchemes{ + SecuritySchemes: openapi3.SecuritySchemes{ "someSecurityScheme": {Ref: "#/components/securitySchemes/otherSecurityScheme"}, "otherSecurityScheme": { - Value: &SecurityScheme{ + Value: &openapi3.SecurityScheme{ Description: "Some security scheme", Type: "apiKey", In: "query", @@ -429,11 +431,11 @@ components: for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - doc := &T{} - err := yaml.Unmarshal([]byte(tt.spec), &doc) + doc := &openapi3.T{} + _, err := yaml.Unmarshal([]byte(tt.spec), &doc, yaml.DecodeOpts{DisableTimestamps: true}) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) if tt.expectedErr != "" { require.EqualError(t, err, tt.expectedErr) } else { @@ -444,21 +446,21 @@ components: } func TestAddRemoveServer(t *testing.T) { - testServerLines := []*Server{{URL: "test0.com"}, {URL: "test1.com"}, {URL: "test3.com"}} + testServerLines := []*openapi3.Server{{URL: "test0.com"}, {URL: "test1.com"}, {URL: "test3.com"}} - doc3 := &T{ + doc3 := &openapi3.T{ OpenAPI: "3.0.3", - Components: &Components{}, + Components: &openapi3.Components{}, } assert.Empty(t, doc3.Servers) - doc3.AddServer(&Server{URL: "testserver1.com"}) + doc3.AddServer(&openapi3.Server{URL: "testserver1.com"}) assert.NotEmpty(t, doc3.Servers) assert.Len(t, doc3.Servers, 1) - doc3.Servers = Servers{} + doc3.Servers = openapi3.Servers{} assert.Empty(t, doc3.Servers) @@ -467,12 +469,38 @@ func TestAddRemoveServer(t *testing.T) { assert.NotEmpty(t, doc3.Servers) assert.Len(t, doc3.Servers, 3) - doc3.Servers = Servers{} + doc3.Servers = openapi3.Servers{} doc3.AddServers(testServerLines...) assert.NotEmpty(t, doc3.Servers) assert.Len(t, doc3.Servers, 3) - doc3.Servers = Servers{} + doc3.Servers = openapi3.Servers{} +} + +func TestOpenAPIMajorMinor(t *testing.T) { + var doc *openapi3.T + require.Equal(t, "", doc.OpenAPIMajorMinor()) + require.False(t, doc.IsOpenAPI30()) + require.False(t, doc.IsOpenAPI31OrLater()) + + doc = &openapi3.T{} + require.Equal(t, "", doc.OpenAPIMajorMinor()) + require.False(t, doc.IsOpenAPI30()) + require.False(t, doc.IsOpenAPI31OrLater()) + + semvers := []string{"3", "3.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4", "3.1", "3.1.0", "3.1.1", "3.1.2", "3.2", "3.2.0"} + mms := []string{"3.0", "3.0", "3.0", "3.0", "3.0", "3.0", "3.0", "3.1", "3.1", "3.1", "3.1", "3.2", "3.2"} + three0s := []bool{true, true, true, true, true, true, true, false, false, false, false, false, false} + three1plusses := []bool{false, false, false, false, false, false, false, true, true, true, true, true, true} + for i := range len(semvers) { + t.Run(fmt.Sprintf("openapi:%s", semvers[i]), func(t *testing.T) { + t.Parallel() + doc := &openapi3.T{OpenAPI: semvers[i]} + require.Equal(t, mms[i], doc.OpenAPIMajorMinor()) + require.Equal(t, three0s[i], doc.IsOpenAPI30()) + require.Equal(t, three1plusses[i], doc.IsOpenAPI31OrLater()) + }) + } } diff --git a/openapi3/openapi3_version_test.go b/openapi3/openapi3_version_test.go new file mode 100644 index 000000000..1bd580a7c --- /dev/null +++ b/openapi3/openapi3_version_test.go @@ -0,0 +1,260 @@ +package openapi3_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestWebhooksField(t *testing.T) { + t.Run("serialize webhooks in OpenAPI 3.1", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "newPet": { + Post: &openapi3.Operation{ + Summary: "New pet webhook", + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Description: openapi3.Ptr("Success"), + }, + }), + ), + }, + }, + }, + } + + data, err := json.Marshal(doc) + require.NoError(t, err) + + // Should contain webhooks + require.Contains(t, string(data), `"webhooks"`) + require.Contains(t, string(data), `"newPet"`) + }) + + t.Run("deserialize webhooks from OpenAPI 3.1", func(t *testing.T) { + jsonData := []byte(`{ + "openapi": "3.1.0", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": {}, + "webhooks": { + "newPet": { + "post": { + "summary": "New pet webhook", + "responses": { + "200": { + "description": "Success" + } + } + } + } + } + }`) + + var doc openapi3.T + err := json.Unmarshal(jsonData, &doc) + require.NoError(t, err) + + require.True(t, doc.IsOpenAPI31OrLater()) + require.NotNil(t, doc.Webhooks) + require.Contains(t, doc.Webhooks, "newPet") + require.NotNil(t, doc.Webhooks["newPet"].Post) + require.Equal(t, "New pet webhook", doc.Webhooks["newPet"].Post.Summary) + }) + + t.Run("OpenAPI 3.0 without webhooks", func(t *testing.T) { + jsonData := []byte(`{ + "openapi": "3.0.3", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "paths": {} + }`) + + var doc openapi3.T + err := json.Unmarshal(jsonData, &doc) + require.NoError(t, err) + + require.True(t, doc.IsOpenAPI30()) + require.Nil(t, doc.Webhooks) + }) + + t.Run("validate webhooks", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "validWebhook": { + Post: &openapi3.Operation{ + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Description: openapi3.Ptr("Success"), + }, + }), + ), + }, + }, + }, + } + + // Should validate successfully + err := doc.Validate(t.Context()) + require.NoError(t, err) + }) + + t.Run("validate fails with nil webhook", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "invalidWebhook": nil, + }, + } + + err := doc.Validate(t.Context()) + require.Error(t, err) + require.ErrorContains(t, err, "webhook") + require.ErrorContains(t, err, "invalidWebhook") + }) +} + +func TestJSONLookupWithWebhooks(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "test": { + Post: &openapi3.Operation{ + Summary: "Test webhook", + }, + }, + }, + } + + result, err := doc.JSONLookup("webhooks") + require.NoError(t, err) + require.NotNil(t, result) + + webhooks, ok := result.(map[string]*openapi3.PathItem) + require.True(t, ok) + require.Contains(t, webhooks, "test") +} + +func TestVersionBasedBehavior(t *testing.T) { + t.Run("detect and handle OpenAPI 3.0", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + } + + if doc.IsOpenAPI30() { + // OpenAPI 3.0 specific logic + require.Nil(t, doc.Webhooks) + } + }) + + t.Run("detect and handle OpenAPI 3.1", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{ + "test": { + Post: &openapi3.Operation{ + Summary: "Test", + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Description: openapi3.Ptr("OK"), + }, + }), + ), + }, + }, + }, + } + + if doc.IsOpenAPI31OrLater() { + // OpenAPI 3.1 specific logic + require.NotNil(t, doc.Webhooks) + require.Contains(t, doc.Webhooks, "test") + } + }) +} + +func TestMigrationScenario(t *testing.T) { + t.Run("upgrade document from 3.0 to 3.1", func(t *testing.T) { + // Start with 3.0 document + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "Test API", + Version: "1.0.0", + }, + Paths: openapi3.NewPaths(), + } + + require.True(t, doc.IsOpenAPI30()) + require.Nil(t, doc.Webhooks) + + // Upgrade to 3.1 + doc.OpenAPI = "3.1.0" + + // Add 3.1 features + doc.Webhooks = map[string]*openapi3.PathItem{ + "newEvent": { + Post: &openapi3.Operation{ + Summary: "New event notification", + Responses: openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: &openapi3.Response{ + Description: openapi3.Ptr("Processed"), + }, + }), + ), + }, + }, + } + + require.True(t, doc.IsOpenAPI31OrLater()) + require.NotNil(t, doc.Webhooks) + + // Validate the upgraded document + err := doc.Validate(t.Context()) + require.NoError(t, err) + }) +} diff --git a/openapi3/operation.go b/openapi3/operation.go index 1039f492b..1ec78cdc2 100644 --- a/openapi3/operation.go +++ b/openapi3/operation.go @@ -3,8 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" - "fmt" + "maps" "strconv" "github.com/go-openapi/jsonpointer" @@ -69,9 +68,7 @@ func (operation Operation) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Operation. func (operation Operation) MarshalYAML() (any, error) { m := make(map[string]any, 12+len(operation.Extensions)) - for k, v := range operation.Extensions { - m[k] = v - } + maps.Copy(m, operation.Extensions) if x := operation.Tags; len(x) != 0 { m["tags"] = x } @@ -192,32 +189,34 @@ func (operation *Operation) AddResponse(status int, response *Response) { // Validate returns an error if Operation does not comply with the OpenAPI spec. func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if v := operation.Parameters; v != nil { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } if v := operation.RequestBody; v != nil { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } if v := operation.Responses; v != nil { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } - } else { - return errors.New("value of responses must be an object") + } else if err := me.emit(newOperationResponsesRequired(operation.Origin)); err != nil { + return err } if v := operation.ExternalDocs; v != nil { - if err := v.Validate(ctx); err != nil { - return fmt.Errorf("invalid external docs: %w", err) + wrap := func(e error) error { return &SectionValidationError{Section: "external docs", Cause: e} } + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - return validateExtensions(ctx, operation.Extensions) + return me.finalize(validateExtensions(ctx, operation.Extensions, operation.Origin)) } diff --git a/openapi3/operation_test.go b/openapi3/operation_test.go index 4c3a7bde5..d23358d7d 100644 --- a/openapi3/operation_test.go +++ b/openapi3/operation_test.go @@ -1,15 +1,15 @@ -package openapi3 +package openapi3_test import ( - "context" - "errors" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) -func initOperation() *Operation { - operation := NewOperation() +func initOperation() *openapi3.Operation { + operation := openapi3.NewOperation() operation.Description = "Some description" operation.Summary = "Some summary" operation.Tags = []string{"tag1", "tag2"} @@ -18,55 +18,59 @@ func initOperation() *Operation { func TestAddParameter(t *testing.T) { operation := initOperation() - operation.AddParameter(NewQueryParameter("param1")) - operation.AddParameter(NewCookieParameter("param2")) + operation.AddParameter(openapi3.NewQueryParameter("param1")) + operation.AddParameter(openapi3.NewCookieParameter("param2")) require.Equal(t, "param1", operation.Parameters.GetByInAndName("query", "param1").Name) require.Equal(t, "param2", operation.Parameters.GetByInAndName("cookie", "param2").Name) } func TestAddResponse(t *testing.T) { operation := initOperation() - operation.AddResponse(200, NewResponse()) - operation.AddResponse(400, NewResponse()) + operation.AddResponse(200, openapi3.NewResponse()) + operation.AddResponse(400, openapi3.NewResponse()) require.NotNil(t, "status 200", operation.Responses.Status(200).Value) require.NotNil(t, "status 400", operation.Responses.Status(400).Value) } -func operationWithoutResponses() *Operation { +func operationWithoutResponses() *openapi3.Operation { operation := initOperation() return operation } -func operationWithResponses() *Operation { +func operationWithResponses() *openapi3.Operation { operation := initOperation() - operation.AddResponse(200, NewResponse().WithDescription("some response")) + operation.AddResponse(200, openapi3.NewResponse().WithDescription("some response")) return operation } func TestOperationValidation(t *testing.T) { tests := []struct { - name string - input *Operation - expectedError error + name string + input *openapi3.Operation + expectedErrorMsg string // empty = expect no error }{ { "when no Responses object is provided", operationWithoutResponses(), - errors.New("value of responses must be an object"), + "value of responses must be an object", }, { "when a Responses object is provided", operationWithResponses(), - nil, + "", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := context.Background() + c := t.Context() validationErr := test.input.Validate(c) - require.Equal(t, test.expectedError, validationErr, "expected errors (or lack of) to match") + if test.expectedErrorMsg == "" { + require.NoError(t, validationErr) + } else { + require.EqualError(t, validationErr, test.expectedErrorMsg) + } }) } } diff --git a/openapi3/origin.go b/openapi3/origin.go index 1a6d12eb1..6547ff589 100644 --- a/openapi3/origin.go +++ b/openapi3/origin.go @@ -27,6 +27,13 @@ type Location struct { Line int `json:"line,omitempty" yaml:"line,omitempty"` Column int `json:"column,omitempty" yaml:"column,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` + + // EndLine and EndColumn mark the end of the block this location heads (set + // only on Origin.Key). For an operation or schema this spans the whole + // block, so a consumer can extract the entire element from its source. + // Both are zero when the underlying YAML carried no end information. + EndLine int `json:"endLine,omitempty" yaml:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty" yaml:"endColumn,omitempty"` } // originFromSeq parses the compact []any sequence produced by yaml3's addOrigin. @@ -99,6 +106,17 @@ func originFromSeq(s []any) *Origin { o.Sequences[sname] = locs } } + + // Trailing block end (yaml3 >= the end-position release): end_delta, end_col. + // Reconstruct the end of the whole block on Origin.Key so a consumer can + // extract the entire element. Older origin sequences omit these, leaving + // EndLine/EndColumn zero. end_col == 0 means no end information was recorded. + if o.Key != nil && idx+1 < len(s) { + if endCol := toInt(s[idx+1]); endCol > 0 { + o.Key.EndLine = keyLine + toInt(s[idx]) + o.Key.EndColumn = endCol + } + } return o } @@ -162,7 +180,7 @@ func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.Origi } // Recurse into exported struct fields using json tags - for i := 0; i < typ.NumField(); i++ { + for i := range typ.NumField() { sf := typ.Field(i) if !sf.IsExported() { continue @@ -179,7 +197,7 @@ func applyOriginsToStruct(val reflect.Value, ptr reflect.Value, tree *yaml.Origi // Handle wrapper types whose inner struct has no json tag: // - *Ref types (e.g. SchemaRef, ResponseRef) have a "Value" field - // - AdditionalProperties has a "Schema" field + // - BoolSchema (AdditionalProperties, UnevaluatedProperties, UnevaluatedItems) has a "Schema" field // The origin tree data applies to the inner struct, not a sub-key. for _, fieldName := range []string{"Value", "Schema"} { vf := val.FieldByName(fieldName) diff --git a/openapi3/origin_load_test.go b/openapi3/origin_load_test.go index 51159186c..438cc2dd9 100644 --- a/openapi3/origin_load_test.go +++ b/openapi3/origin_load_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -48,7 +47,7 @@ func TestOrigin_LoadAllTestdata(t *testing.T) { loader := NewLoader() loader.IncludeOrigin = true loader.IsExternalRefsAllowed = tc.externalRefs - loader.Context = context.Background() + loader.Context = t.Context() _, err := loader.LoadFromFile(tc.file) require.NoError(t, err) diff --git a/openapi3/origin_test.go b/openapi3/origin_test.go index d30efce67..5cbb9d0e5 100644 --- a/openapi3/origin_test.go +++ b/openapi3/origin_test.go @@ -1,33 +1,59 @@ -package openapi3 +package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) -func TestOrigin_Info(t *testing.T) { - loader := NewLoader() +const originKey = "__origin__" + +func TestOrigin_T(t *testing.T) { + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/simple.yaml") require.NoError(t, err) - require.NotNil(t, doc.Info.Origin) + require.NotNil(t, doc.Origin) + require.NotNil(t, doc.Origin.Key) require.Equal(t, - &Location{ + openapi3.Location{ File: "testdata/origin/simple.yaml", - Line: 2, + Line: 1, Column: 1, - Name: "info", + Name: "openapi", + }, + doc.Origin.Fields["openapi"]) +} + +func TestOrigin_Info(t *testing.T) { + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.IncludeOrigin = true + loader.Context = t.Context() + + doc, err := loader.LoadFromFile("testdata/origin/simple.yaml") + require.NoError(t, err) + + require.NotNil(t, doc.Info.Origin) + require.Equal(t, + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 2, + Column: 1, + Name: "info", + EndLine: 4, + EndColumn: 14, }, doc.Info.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/simple.yaml", Line: 3, Column: 3, @@ -36,7 +62,7 @@ func TestOrigin_Info(t *testing.T) { doc.Info.Origin.Fields["title"]) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/simple.yaml", Line: 4, Column: 3, @@ -46,21 +72,23 @@ func TestOrigin_Info(t *testing.T) { } func TestOrigin_Paths(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/simple.yaml") require.NoError(t, err) require.NotNil(t, doc.Paths.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 5, - Column: 1, - Name: "paths", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 5, + Column: 1, + Name: "paths", + EndLine: 19, + EndColumn: 31, }, doc.Paths.Origin.Key) @@ -68,30 +96,38 @@ func TestOrigin_Paths(t *testing.T) { require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 13, - Column: 3, - Name: "/partner-api/test/another-method", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 13, + Column: 3, + Name: "/partner-api/test/another-method", + EndLine: 19, + EndColumn: 31, }, base.Origin.Key) + // The operation's Origin.Key spans the whole endpoint block: it starts at + // `get:` (line 14) and ends at the operation's last content (line 19). This + // is what lets a consumer point at the endpoint's location, not just the + // exact change site. require.NotNil(t, base.Get.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 14, - Column: 5, - Name: "get", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 14, + Column: 5, + Name: "get", + EndLine: 19, + EndColumn: 31, }, base.Get.Origin.Key) } func TestOrigin_RequestBody(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/request_body.yaml") require.NoError(t, err) @@ -99,30 +135,34 @@ func TestOrigin_RequestBody(t *testing.T) { base := doc.Paths.Find("/subscribe").Post.RequestBody.Value require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/request_body.yaml", - Line: 8, - Column: 7, - Name: "requestBody", + &openapi3.Location{ + File: "testdata/origin/request_body.yaml", + Line: 8, + Column: 7, + Name: "requestBody", + EndLine: 19, + EndColumn: 31, }, base.Origin.Key) require.NotNil(t, base.Content["application/json"].Origin) require.Equal(t, - &Location{ - File: "testdata/origin/request_body.yaml", - Line: 10, - Column: 11, - Name: "application/json", + &openapi3.Location{ + File: "testdata/origin/request_body.yaml", + Line: 10, + Column: 11, + Name: "application/json", + EndLine: 19, + EndColumn: 31, }, base.Content["application/json"].Origin.Key) } func TestOrigin_Responses(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/simple.yaml") require.NoError(t, err) @@ -130,11 +170,13 @@ func TestOrigin_Responses(t *testing.T) { base := doc.Paths.Find("/partner-api/test/another-method").Get.Responses require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 17, - Column: 7, - Name: "responses", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 17, + Column: 7, + Name: "responses", + EndLine: 19, + EndColumn: 31, }, base.Origin.Key) @@ -142,24 +184,28 @@ func TestOrigin_Responses(t *testing.T) { // ResponseRef.Origin is populated with the same data as Value.Origin require.NotNil(t, base.Value("200").Origin) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 18, - Column: 9, - Name: "200", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 18, + Column: 9, + Name: "200", + EndLine: 19, + EndColumn: 31, }, base.Value("200").Origin.Key) require.Equal(t, - &Location{ - File: "testdata/origin/simple.yaml", - Line: 18, - Column: 9, - Name: "200", + &openapi3.Location{ + File: "testdata/origin/simple.yaml", + Line: 18, + Column: 9, + Name: "200", + EndLine: 19, + EndColumn: 31, }, base.Value("200").Value.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/simple.yaml", Line: 19, Column: 11, @@ -169,10 +215,10 @@ func TestOrigin_Responses(t *testing.T) { } func TestOrigin_Parameters(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/parameters.yaml") require.NoError(t, err) @@ -180,16 +226,18 @@ func TestOrigin_Parameters(t *testing.T) { base := doc.Paths.Find("/api/test").Get.Parameters[0].Value require.NotNil(t, base) require.Equal(t, - &Location{ - File: "testdata/origin/parameters.yaml", - Line: 9, - Column: 11, - Name: "name", + &openapi3.Location{ + File: "testdata/origin/parameters.yaml", + Line: 9, + Column: 11, + Name: "name", + EndLine: 12, + EndColumn: 26, }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/parameters.yaml", Line: 10, Column: 11, @@ -198,7 +246,7 @@ func TestOrigin_Parameters(t *testing.T) { base.Origin.Fields["in"]) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/parameters.yaml", Line: 9, Column: 11, @@ -208,10 +256,10 @@ func TestOrigin_Parameters(t *testing.T) { } func TestOrigin_SchemaInAdditionalProperties(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/additional_properties.yaml") require.NoError(t, err) @@ -221,16 +269,18 @@ func TestOrigin_SchemaInAdditionalProperties(t *testing.T) { require.NotNil(t, base.Schema.Value.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/additional_properties.yaml", - Line: 14, - Column: 17, - Name: "additionalProperties", + &openapi3.Location{ + File: "testdata/origin/additional_properties.yaml", + Line: 14, + Column: 17, + Name: "additionalProperties", + EndLine: 20, + EndColumn: 35, }, base.Schema.Value.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/additional_properties.yaml", Line: 15, Column: 19, @@ -240,10 +290,10 @@ func TestOrigin_SchemaInAdditionalProperties(t *testing.T) { } func TestOrigin_ExternalDocs(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/external_docs.yaml") require.NoError(t, err) @@ -252,16 +302,18 @@ func TestOrigin_ExternalDocs(t *testing.T) { require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/external_docs.yaml", - Line: 13, - Column: 1, - Name: "externalDocs", + &openapi3.Location{ + File: "testdata/origin/external_docs.yaml", + Line: 13, + Column: 1, + Name: "externalDocs", + EndLine: 15, + EndColumn: 38, }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/external_docs.yaml", Line: 14, Column: 3, @@ -270,7 +322,7 @@ func TestOrigin_ExternalDocs(t *testing.T) { base.Origin.Fields["description"]) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/external_docs.yaml", Line: 15, Column: 3, @@ -280,10 +332,10 @@ func TestOrigin_ExternalDocs(t *testing.T) { } func TestOrigin_Security(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/security.yaml") require.NoError(t, err) @@ -292,16 +344,18 @@ func TestOrigin_Security(t *testing.T) { require.NotNil(t, base) require.Equal(t, - &Location{ - File: "testdata/origin/security.yaml", - Line: 29, - Column: 5, - Name: "petstore_auth", + &openapi3.Location{ + File: "testdata/origin/security.yaml", + Line: 29, + Column: 5, + Name: "petstore_auth", + EndLine: 36, + EndColumn: 38, }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/security.yaml", Line: 30, Column: 7, @@ -310,25 +364,29 @@ func TestOrigin_Security(t *testing.T) { base.Origin.Fields["type"]) require.Equal(t, - &Location{ - File: "testdata/origin/security.yaml", - Line: 31, - Column: 7, - Name: "flows", + &openapi3.Location{ + File: "testdata/origin/security.yaml", + Line: 31, + Column: 7, + Name: "flows", + EndLine: 36, + EndColumn: 38, }, base.Flows.Origin.Key) require.Equal(t, - &Location{ - File: "testdata/origin/security.yaml", - Line: 32, - Column: 9, - Name: "implicit", + &openapi3.Location{ + File: "testdata/origin/security.yaml", + Line: 32, + Column: 9, + Name: "implicit", + EndLine: 36, + EndColumn: 38, }, base.Flows.Implicit.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/security.yaml", Line: 33, Column: 11, @@ -338,10 +396,10 @@ func TestOrigin_Security(t *testing.T) { } func TestOrigin_Example(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/example.yaml") require.NoError(t, err) @@ -349,16 +407,18 @@ func TestOrigin_Example(t *testing.T) { base := doc.Paths.Find("/subscribe").Post.RequestBody.Value.Content["application/json"].Examples["bar"].Value require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/example.yaml", - Line: 14, - Column: 15, - Name: "bar", + &openapi3.Location{ + File: "testdata/origin/example.yaml", + Line: 14, + Column: 15, + Name: "bar", + EndLine: 16, + EndColumn: 38, // just past the closing `}` of the flow map `{"bar": "baz"}` }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/example.yaml", Line: 15, Column: 17, @@ -373,10 +433,10 @@ func TestOrigin_Example(t *testing.T) { } func TestOrigin_XML(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/xml.yaml") require.NoError(t, err) @@ -384,16 +444,18 @@ func TestOrigin_XML(t *testing.T) { base := doc.Paths.Find("/subscribe").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["name"].Value.XML require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/xml.yaml", - Line: 21, - Column: 19, - Name: "xml", + &openapi3.Location{ + File: "testdata/origin/xml.yaml", + Line: 21, + Column: 19, + Name: "xml", + EndLine: 23, + EndColumn: 35, }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/xml.yaml", Line: 22, Column: 21, @@ -402,7 +464,7 @@ func TestOrigin_XML(t *testing.T) { base.Origin.Fields["namespace"]) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/xml.yaml", Line: 23, Column: 21, @@ -417,7 +479,7 @@ func TestOrigin_XML(t *testing.T) { // These fields have no dedicated UnmarshalJSON; extractOrigins strips // __origin__ before JSON marshaling so it never reaches these values. func TestOrigin_AnyFieldsStripped(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/any_fields.yaml") require.NoError(t, err) @@ -456,7 +518,7 @@ func TestOrigin_AnyFieldsStripped(t *testing.T) { } func TestOrigin_ExampleWithArrayValue(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/example_with_array.yaml") require.NoError(t, err) @@ -475,6 +537,40 @@ func TestOrigin_ExampleWithArrayValue(t *testing.T) { // TestOrigin_OriginExistsInProperties verifies that loading fails when a specification // contains a property named "__origin__", highlighting a limitation in the current implementation. +func TestOrigin_ConstAndExamplesStripped(t *testing.T) { + var data = ` +openapi: "3.1.0" +info: + title: Test + version: "1.0" +paths: {} +components: + schemas: + Foo: + type: object + const: {x: reuven} + examples: + - {y: value} +` + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + + doc, err := loader.LoadFromData([]byte(data)) + require.NoError(t, err) + + schema := doc.Components.Schemas["Foo"].Value + require.NotNil(t, schema) + + constMap, ok := schema.Const.(map[string]any) + require.True(t, ok) + require.NotContains(t, constMap, originKey) + + require.Len(t, schema.Examples, 1) + exampleMap, ok := schema.Examples[0].(map[string]any) + require.True(t, ok) + require.NotContains(t, exampleMap, originKey) +} + func TestOrigin_OriginExistsInProperties(t *testing.T) { var data = ` paths: @@ -496,7 +592,7 @@ components: type: string ` - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true _, err := loader.LoadFromData([]byte(data)) @@ -511,7 +607,7 @@ components: // from the yaml3 decoder but it was never stripped, causing spurious diffs // between specs loaded from different file paths. func TestOrigin_ExtensionValuesStripped(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/extensions.yaml") @@ -537,11 +633,11 @@ func TestOrigin_ExtensionValuesStripped(t *testing.T) { } func TestOrigin_WithExternalRef(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/external.yaml") require.NoError(t, err) @@ -549,16 +645,18 @@ func TestOrigin_WithExternalRef(t *testing.T) { base := doc.Paths.Find("/subscribe").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["name"].Value require.NotNil(t, base.XML.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/external-schema.yaml", - Line: 2, - Column: 1, - Name: "xml", + &openapi3.Location{ + File: "testdata/origin/external-schema.yaml", + Line: 2, + Column: 1, + Name: "xml", + EndLine: 4, + EndColumn: 17, }, base.XML.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/external-schema.yaml", Line: 3, Column: 3, @@ -567,7 +665,7 @@ func TestOrigin_WithExternalRef(t *testing.T) { base.XML.Origin.Fields["namespace"]) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/external-schema.yaml", Line: 4, Column: 3, @@ -582,10 +680,10 @@ func TestOrigin_WithExternalRef(t *testing.T) { // root mapping of a document was skipped. This test covers the fix in yaml3's // document() decoder that injects __origin__ for the root mapping too. func TestOrigin_WithExternalRefRootOrigin(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true loader.IncludeOrigin = true - loader.Context = context.Background() + loader.Context = t.Context() doc, err := loader.LoadFromFile("testdata/origin/external.yaml") require.NoError(t, err) @@ -596,16 +694,18 @@ func TestOrigin_WithExternalRefRootOrigin(t *testing.T) { // Root schema Origin must now be set (fixed in yaml3 document() injection) require.NotNil(t, base.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/external-schema.yaml", - Line: 1, - Column: 1, - Name: "", + &openapi3.Location{ + File: "testdata/origin/external-schema.yaml", + Line: 1, + Column: 1, + Name: "", + EndLine: 4, + EndColumn: 17, }, base.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/external-schema.yaml", Line: 1, Column: 1, @@ -619,7 +719,7 @@ func TestOrigin_WithExternalRefRootOrigin(t *testing.T) { // The if k == originKey blocks in their UnmarshalJSON were removed; this // confirms extractOrigins strips __origin__ before it reaches those iterators. func TestOrigin_MaplikeNoOriginKey(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/simple.yaml") require.NoError(t, err) @@ -633,7 +733,7 @@ func TestOrigin_MaplikeNoOriginKey(t *testing.T) { } func TestOrigin_NoSpuriousOriginsInComponents(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/components.yaml") @@ -656,7 +756,7 @@ func TestOrigin_NoSpuriousOriginsInComponents(t *testing.T) { // These locations are used by NewSourceFromSequenceItem to pinpoint // breaking changes to individual required field names. func TestOrigin_RequiredSequence(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/required_sequence.yaml") @@ -673,14 +773,14 @@ func TestOrigin_RequiredSequence(t *testing.T) { require.True(t, ok, "Origin.Sequences must contain 'required'") require.Len(t, seqLocs, 2) - require.Equal(t, Location{ + require.Equal(t, openapi3.Location{ File: "testdata/origin/required_sequence.yaml", Line: 14, Column: 19, Name: "name", }, seqLocs[0]) - require.Equal(t, Location{ + require.Equal(t, openapi3.Location{ File: "testdata/origin/required_sequence.yaml", Line: 15, Column: 19, @@ -692,7 +792,7 @@ func TestOrigin_RequiredSequence(t *testing.T) { // without error and carries origin metadata from the anchor definition. // Multiple aliases of the same anchor must not produce duplicate __origin__ keys. func TestOrigin_YAMLAlias(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/alias.yaml") @@ -703,11 +803,13 @@ func TestOrigin_YAMLAlias(t *testing.T) { alias2 := doc.Components.Schemas["Alias2"].Value // All three point to the same anchor node, so origin reflects the anchor location. - anchorLoc := &Location{ - File: "testdata/origin/alias.yaml", - Line: 7, - Column: 5, - Name: "Base", + anchorLoc := &openapi3.Location{ + File: "testdata/origin/alias.yaml", + Line: 7, + Column: 5, + Name: "Base", + EndLine: 13, + EndColumn: 23, } require.Equal(t, anchorLoc, anchor.Origin.Key) require.Equal(t, anchorLoc, alias1.Origin.Key) @@ -716,7 +818,7 @@ func TestOrigin_YAMLAlias(t *testing.T) { // TestOrigin_Headers verifies that response header origin is tracked correctly. func TestOrigin_Headers(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/headers.yaml") @@ -725,16 +827,18 @@ func TestOrigin_Headers(t *testing.T) { headers := doc.Paths.Find("/items").Get.Responses.Value("200").Value.Headers require.Equal(t, - &Location{ - File: "testdata/origin/headers.yaml", - Line: 12, - Column: 13, - Name: "X-Rate-Limit", + &openapi3.Location{ + File: "testdata/origin/headers.yaml", + Line: 12, + Column: 13, + Name: "X-Rate-Limit", + EndLine: 15, + EndColumn: 30, }, headers["X-Rate-Limit"].Value.Origin.Key) require.Equal(t, - Location{ + openapi3.Location{ File: "testdata/origin/headers.yaml", Line: 13, Column: 15, @@ -743,11 +847,13 @@ func TestOrigin_Headers(t *testing.T) { headers["X-Rate-Limit"].Value.Origin.Fields["description"]) require.Equal(t, - &Location{ - File: "testdata/origin/headers.yaml", - Line: 16, - Column: 13, - Name: "X-Request-Id", + &openapi3.Location{ + File: "testdata/origin/headers.yaml", + Line: 16, + Column: 13, + Name: "X-Request-Id", + EndLine: 19, + EndColumn: 29, }, headers["X-Request-Id"].Value.Origin.Key) } @@ -757,7 +863,7 @@ func TestOrigin_Headers(t *testing.T) { // strings ("200":). Bare integers produce map[any]any in the // YAML decoder, which required a dedicated fix in extractOrigins. func TestOrigin_IntegerStatusCode(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() loader.IncludeOrigin = true doc, err := loader.LoadFromFile("testdata/origin/parameters.yaml") @@ -766,22 +872,26 @@ func TestOrigin_IntegerStatusCode(t *testing.T) { resp200 := doc.Paths.Find("/api/test").Get.Responses.Value("200").Value require.NotNil(t, resp200.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/parameters.yaml", - Line: 14, - Column: 9, - Name: "200", + &openapi3.Location{ + File: "testdata/origin/parameters.yaml", + Line: 14, + Column: 9, + Name: "200", + EndLine: 15, + EndColumn: 26, }, resp200.Origin.Key) resp201 := doc.Paths.Find("/api/test").Post.Responses.Value("201").Value require.NotNil(t, resp201.Origin) require.Equal(t, - &Location{ - File: "testdata/origin/parameters.yaml", - Line: 18, - Column: 9, - Name: "201", + &openapi3.Location{ + File: "testdata/origin/parameters.yaml", + Line: 18, + Column: 9, + Name: "201", + EndLine: 19, + EndColumn: 26, }, resp201.Origin.Key) } @@ -789,7 +899,7 @@ func TestOrigin_IntegerStatusCode(t *testing.T) { // TestOrigin_Disabled verifies that all Origin fields are nil when // IncludeOrigin is false (the default), ensuring no overhead in the common case. func TestOrigin_Disabled(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() // IncludeOrigin defaults to false doc, err := loader.LoadFromFile("testdata/origin/required_sequence.yaml") @@ -800,3 +910,50 @@ func TestOrigin_Disabled(t *testing.T) { require.Nil(t, doc.Info.Origin) require.Nil(t, doc.Paths.Origin) } + +// TestOrigin_MappingFields verifies that mapping-valued schema fields +// (dependentRequired, dependentSchemas, patternProperties) have their +// key locations tracked in Origin.Fields. Before yaml3 v0.0.12, +// buildOriginSeq only tracked scalar and sequence values, so these +// mapping-valued fields were missing from the origin and source +// location lookups returned nil. +func TestOrigin_MappingFields(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + + doc, err := loader.LoadFromFile("testdata/origin/mapping_fields.yaml") + require.NoError(t, err) + + schema := doc.Paths.Find("/test").Get.Responses.Value("200").Value. + Content["application/json"].Schema.Value.Properties["metadata"].Value + require.NotNil(t, schema.Origin) + + file := "testdata/origin/mapping_fields.yaml" + + // dependentRequired is a map[string][]string — mapping-valued + require.Contains(t, schema.Origin.Fields, "dependentRequired") + require.Equal(t, openapi3.Location{ + File: file, + Line: 18, + Column: 21, + Name: "dependentRequired", + }, schema.Origin.Fields["dependentRequired"]) + + // dependentSchemas is a Schemas map — mapping-valued + require.Contains(t, schema.Origin.Fields, "dependentSchemas") + require.Equal(t, openapi3.Location{ + File: file, + Line: 22, + Column: 21, + Name: "dependentSchemas", + }, schema.Origin.Fields["dependentSchemas"]) + + // patternProperties is a Schemas map — mapping-valued + require.Contains(t, schema.Origin.Fields, "patternProperties") + require.Equal(t, openapi3.Location{ + File: file, + Line: 25, + Column: 21, + Name: "patternProperties", + }, schema.Origin.Fields["patternProperties"]) +} diff --git a/openapi3/parameter.go b/openapi3/parameter.go index 6099ef3ed..b1010a61e 100644 --- a/openapi3/parameter.go +++ b/openapi3/parameter.go @@ -3,9 +3,8 @@ package openapi3 import ( "context" "encoding/json" - "errors" "fmt" - "slices" + "maps" "strconv" "github.com/go-openapi/jsonpointer" @@ -57,7 +56,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt if v := parameterRef.Value; v != nil { key := v.In + ":" + v.Name if _, ok := dupes[key]; ok { - return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name) + return newDuplicateParameter(v.In, v.Name, v.Origin) } dupes[key] = struct{}{} } @@ -161,9 +160,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Parameter. func (parameter Parameter) MarshalYAML() (any, error) { m := make(map[string]any, 13+len(parameter.Extensions)) - for k, v := range parameter.Extensions { - m[k] = v - } + maps.Copy(m, parameter.Extensions) if x := parameter.Name; x != "" { m["name"] = x @@ -313,7 +310,7 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti ctx = WithValidationOptions(ctx, opts...) if parameter.Name == "" { - return errors.New("parameter name can't be blank") + return newParameterNameRequired(parameter.Origin) } in := parameter.In switch in { @@ -323,11 +320,11 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti ParameterInHeader, ParameterInCookie: default: - return fmt.Errorf("parameter can't have 'in' value %q", parameter.In) + return newInvalidParameterIn(parameter.In, parameter.Origin) } if in == ParameterInPath && !parameter.Required { - return fmt.Errorf("path parameter %q must be required", parameter.Name) + return newPathParameterRequired(parameter.Name, parameter.Origin) } // Validate a parameter's serialization method. @@ -360,32 +357,32 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti smSupported = true } if !smSupported { - e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in) - return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) + e := newInvalidSerializationMethod(in, sm.Style, sm.Explode, parameter.Origin) + return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema", Cause: e} } if (parameter.Schema == nil) == (len(parameter.Content) == 0) { - e := errors.New("parameter must contain exactly one of content and schema") - return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) + return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema", + Cause: newParameterContentSchemaExactlyOne(parameter.Origin)} } if content := parameter.Content; content != nil { - e := errors.New("parameter content must only contain one entry") if len(content) > 1 { - return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, e) + return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "content", + Cause: newParameterContentSingleEntry(parameter.Origin)} } if err := content.Validate(ctx); err != nil { - return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, err) + return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "content", Cause: err} } } if schema := parameter.Schema; schema != nil { if err := schema.Validate(ctx); err != nil { - return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, err) + return &ParameterFieldValidationError{ParameterName: parameter.Name, Field: "schema", Cause: err} } if parameter.Example != nil && parameter.Examples != nil { - return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name) + return newParameterExampleAndExamplesExclusive(parameter.Name, parameter.Origin) } if vo := getValidationOptions(ctx); vo.examplesValidationDisabled { @@ -393,27 +390,24 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti } if example := parameter.Example; example != nil { if err := validateExampleValue(ctx, example, schema.Value); err != nil { - return fmt.Errorf("invalid example: %w", err) + return newSchemaValueError("example", err, parameter.Origin) } } else if examples := parameter.Examples; examples != nil { - names := make([]string, 0, len(examples)) - for name := range examples { - names = append(names, name) - } - slices.Sort(names) - for _, k := range names { + for _, k := range componentNames(examples) { v := examples[k] if err := v.Validate(ctx); err != nil { - return fmt.Errorf("%s: %w", k, err) + return &ParameterExampleValidationError{ExampleName: k, Cause: err} } if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { - return fmt.Errorf("%s: %w", k, err) + return newSchemaValueError("example", + &ParameterExampleValidationError{ExampleName: k, Cause: err}, + exampleValueOrigin(v.Value, parameter.Origin)) } } } } - return validateExtensions(ctx, parameter.Extensions) + return validateExtensions(ctx, parameter.Extensions, parameter.Origin) } // UnmarshalJSON sets ParametersMap to a copy of data. diff --git a/openapi3/parameter_issue223_test.go b/openapi3/parameter_issue223_test.go index 82660885d..f4f187c9a 100644 --- a/openapi3/parameter_issue223_test.go +++ b/openapi3/parameter_issue223_test.go @@ -1,10 +1,11 @@ -package openapi3 +package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestPathParametersMatchPath(t *testing.T) { @@ -109,9 +110,9 @@ components: type: string `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.EqualError(t, err, `invalid paths: operation GET /pets/{petId} must define exactly all path parameters (missing: [petId])`) } diff --git a/openapi3/parameter_issue834_test.go b/openapi3/parameter_issue834_test.go index 1a1532168..178a0270d 100644 --- a/openapi3/parameter_issue834_test.go +++ b/openapi3/parameter_issue834_test.go @@ -1,10 +1,11 @@ -package openapi3 +package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestPathItemParametersAreValidated(t *testing.T) { @@ -34,10 +35,10 @@ paths: description: A paged array of pets `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.EqualError(t, err, `invalid paths: invalid path /pets: parameter can't have 'in' value "invalid"`) } @@ -73,10 +74,10 @@ paths: description: A paged array of pets `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.EqualError(t, err, `invalid paths: invalid path /pets: parameter "test" content is invalid: parameter content must only contain one entry`) } @@ -106,9 +107,9 @@ paths: description: A paged array of pets `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.EqualError(t, err, `invalid paths: invalid path /pets: parameter "test" schema is invalid: parameter must contain exactly one of content and schema`) } diff --git a/openapi3/path_item.go b/openapi3/path_item.go index 237f5fa6b..701d9911a 100644 --- a/openapi3/path_item.go +++ b/openapi3/path_item.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "fmt" + "maps" "net/http" - "slices" ) // PathItem is specified by OpenAPI/Swagger standard version 3. @@ -46,9 +46,7 @@ func (pathItem PathItem) MarshalYAML() (any, error) { } m := make(map[string]any, 13+len(pathItem.Extensions)) - for k, v := range pathItem.Extensions { - m[k] = v - } + maps.Copy(m, pathItem.Extensions) if x := pathItem.Summary; x != "" { m["summary"] = x } @@ -205,28 +203,25 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) { // Validate returns an error if PathItem does not comply with the OpenAPI spec. func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) operations := pathItem.Operations() - methods := make([]string, 0, len(operations)) - for method := range operations { - methods = append(methods, method) - } - slices.Sort(methods) - for _, method := range methods { + for _, method := range componentNames(operations) { operation := operations[method] - if err := operation.Validate(ctx); err != nil { - return fmt.Errorf("invalid operation %s: %v", method, err) + wrapOp := func(e error) error { return &OperationValidationError{Method: method, Cause: e} } + if err := me.emitWrapped(wrapOp, operation.Validate(ctx)); err != nil { + return err } } if v := pathItem.Parameters; v != nil { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } - return validateExtensions(ctx, pathItem.Extensions) + return me.finalize(validateExtensions(ctx, pathItem.Extensions, pathItem.Origin)) } // isEmpty's introduced in 546590b1 diff --git a/openapi3/paths.go b/openapi3/paths.go index 7075dfc70..048a82403 100644 --- a/openapi3/paths.go +++ b/openapi3/paths.go @@ -3,7 +3,6 @@ package openapi3 import ( "cmp" "context" - "fmt" "slices" "strings" ) @@ -41,18 +40,20 @@ func WithPath(path string, pathItem *PathItem) NewPathsOption { // Validate returns an error if Paths does not comply with the OpenAPI spec. func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) normalizedPaths := make(map[string]string, paths.Len()) - keys := make([]string, 0, paths.Len()) - for key := range paths.Map() { - keys = append(keys, key) - } - slices.Sort(keys) - for _, path := range keys { + for _, path := range paths.Keys() { pathItem := paths.Value(path) if path == "" || path[0] != '/' { - return fmt.Errorf("path %q does not start with a forward slash (/)", path) + if err := me.emit(newPathMustStartWithSlash(path, paths.Origin)); err != nil { + return err + } + // Skip validating operations under a malformed path key: any + // findings below would be addressed under a path that has no + // resolution path until the key itself is fixed. + continue } if pathItem == nil { @@ -62,9 +63,16 @@ func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) erro normalizedPath, _, varsInPath := normalizeTemplatedPath(path) if oldPath, ok := normalizedPaths[normalizedPath]; ok { - return fmt.Errorf("conflicting paths %q and %q", path, oldPath) + if err := me.emit(newConflictingPaths(path, oldPath, paths.Origin)); err != nil { + return err + } + // Skip validating operations under a duplicate path: the + // first occurrence already validated its operations under the + // canonical path, so re-running would surface duplicate-but- + // identical findings without new information. + continue } - normalizedPaths[path] = path + normalizedPaths[normalizedPath] = path var commonParams []string for _, parameterRef := range pathItem.Parameters { @@ -75,12 +83,7 @@ func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) erro } } operations := pathItem.Operations() - methods := make([]string, 0, len(operations)) - for method := range operations { - methods = append(methods, method) - } - slices.Sort(methods) - for _, method := range methods { + for _, method := range componentNames(operations) { operation := operations[method] var setParams []string for _, parameterRef := range operation.Parameters { @@ -102,38 +105,36 @@ func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) erro missing[name] = struct{}{} } } - for name := range varsInPath { - got := false - for _, othername := range definedParams { - if othername == name { - got = true - break - } - } - if !got { - missing[name] = struct{}{} + for _, name := range componentNames(varsInPath) { + if slices.Contains(definedParams, name) { + break } + missing[name] = struct{}{} } if len(missing) != 0 { - missings := make([]string, 0, len(missing)) - for name := range missing { - missings = append(missings, name) + if err := me.emit(&PathParametersError{ + Path: path, + Method: method, + Missing: componentNames(missing), + Origin: pathItem.Origin, + }); err != nil { + return err } - return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings) } } } - if err := pathItem.Validate(ctx); err != nil { - return fmt.Errorf("invalid path %s: %v", path, err) + wrapPath := func(e error) error { return &PathValidationError{Path: path, Cause: e} } + if err := me.emitWrapped(wrapPath, pathItem.Validate(ctx)); err != nil { + return err } } - if err := paths.validateUniqueOperationIDs(); err != nil { + if err := me.emit(paths.validateUniqueOperationIDs()); err != nil { return err } - return validateExtensions(ctx, paths.Extensions) + return me.finalize(validateExtensions(ctx, paths.Extensions, paths.Origin)) } // InMatchingOrder returns paths in the order they are matched against URLs. @@ -187,10 +188,11 @@ func (paths *Paths) Find(key string) *PathItem { } normalizedPath, expected, _ := normalizeTemplatedPath(key) - for path, pathItem := range paths.Map() { + pathsMap := paths.Map() + for _, path := range componentNames(pathsMap) { pathNormalized, got, _ := normalizeTemplatedPath(path) if got == expected && pathNormalized == normalizedPath { - return pathItem + return pathsMap[path] } } return nil @@ -198,11 +200,15 @@ func (paths *Paths) Find(key string) *PathItem { func (paths *Paths) validateUniqueOperationIDs() error { operationIDs := make(map[string]string) - for urlPath, pathItem := range paths.Map() { + pathsMap := paths.Map() + for _, urlPath := range componentNames(pathsMap) { + pathItem := pathsMap[urlPath] if pathItem == nil { continue } - for httpMethod, operation := range pathItem.Operations() { + operations := pathItem.Operations() + for _, httpMethod := range componentNames(operations) { + operation := operations[httpMethod] if operation == nil || operation.OperationID == "" { continue } @@ -211,8 +217,7 @@ func (paths *Paths) validateUniqueOperationIDs() error { if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests. endpoint, endpointDup = endpointDup, endpoint } - return fmt.Errorf("operations %q and %q have the same operation id %q", - endpoint, endpointDup, operation.OperationID) + return newDuplicateOperationID(endpoint, endpointDup, operation.OperationID, operation.Origin) } operationIDs[operation.OperationID] = endpoint } diff --git a/openapi3/paths_test.go b/openapi3/paths_test.go index ad7a921b9..669a9c6b0 100644 --- a/openapi3/paths_test.go +++ b/openapi3/paths_test.go @@ -1,10 +1,11 @@ -package openapi3 +package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestPathsValidate(t *testing.T) { @@ -80,12 +81,12 @@ paths: for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData([]byte(tt.spec[1:])) require.NoError(t, err) - err = doc.Paths.Validate(context.Background()) + err = doc.Paths.Validate(t.Context()) if tt.wantErr == "" { require.NoError(t, err) return diff --git a/openapi3/race_test.go b/openapi3/race_test.go index 405942520..56e2a7ae8 100644 --- a/openapi3/race_test.go +++ b/openapi3/race_test.go @@ -1,7 +1,6 @@ package openapi3_test import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -12,7 +11,7 @@ import ( func TestRaceyPatternSchemaValidateHindersIt(t *testing.T) { schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$") - err := schema.Validate(context.Background()) + err := schema.Validate(t.Context()) require.NoError(t, err) visit := func() { @@ -27,7 +26,7 @@ func TestRaceyPatternSchemaValidateHindersIt(t *testing.T) { func TestRaceyPatternSchemaForIssue775(t *testing.T) { schema := openapi3.NewStringSchema().WithPattern("^test|for|race|condition$") - // err := schema.Validate(context.Background()) + // err := schema.Validate(t.Context()) // require.NoError(t, err) visit := func() { diff --git a/openapi3/ref.go b/openapi3/ref.go index f08367e60..095178dfe 100644 --- a/openapi3/ref.go +++ b/openapi3/ref.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "encoding/json" + "maps" ) //go:generate go run refsgenerator.go @@ -18,9 +19,7 @@ type Ref struct { // MarshalYAML returns the YAML encoding of Ref. func (x Ref) MarshalYAML() (any, error) { m := make(map[string]any, 1+len(x.Extensions)) - for k, v := range x.Extensions { - m[k] = v - } + maps.Copy(m, x.Extensions) if x := x.Ref; x != "" { m["$ref"] = x } @@ -39,5 +38,5 @@ func (x Ref) MarshalJSON() ([]byte, error) { // Validate returns an error if Extensions does not comply with the OpenAPI spec. func (e *Ref) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - return validateExtensions(ctx, e.Extensions) + return validateExtensions(ctx, e.Extensions, e.Origin) } diff --git a/openapi3/refs.go b/openapi3/refs.go index 76b7e0d2d..ace377e33 100644 --- a/openapi3/refs.go +++ b/openapi3/refs.go @@ -1,12 +1,10 @@ -// Code generated by go generate; DO NOT EDIT. +// Code generated by go generate using refs.tmpl; DO NOT EDIT refs.go. package openapi3 import ( "context" "encoding/json" - "fmt" "net/url" - "slices" "strings" "github.com/go-openapi/jsonpointer" @@ -75,11 +73,7 @@ func (x *CallbackRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -94,47 +88,52 @@ func (x *CallbackRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if CallbackRef does not comply with the OpenAPI spec. -func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if CallbackRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *CallbackRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if CallbackRef does not comply with the OpenAPI spec. +func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -213,11 +212,7 @@ func (x *ExampleRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -232,47 +227,52 @@ func (x *ExampleRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if ExampleRef does not comply with the OpenAPI spec. -func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if ExampleRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *ExampleRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if ExampleRef does not comply with the OpenAPI spec. +func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -351,11 +351,7 @@ func (x *HeaderRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -370,47 +366,52 @@ func (x *HeaderRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if HeaderRef does not comply with the OpenAPI spec. -func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if HeaderRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *HeaderRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if HeaderRef does not comply with the OpenAPI spec. +func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -489,11 +490,7 @@ func (x *LinkRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -508,47 +505,52 @@ func (x *LinkRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if LinkRef does not comply with the OpenAPI spec. -func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if LinkRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *LinkRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if LinkRef does not comply with the OpenAPI spec. +func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -627,11 +629,7 @@ func (x *ParameterRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -646,47 +644,52 @@ func (x *ParameterRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if ParameterRef does not comply with the OpenAPI spec. -func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if ParameterRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *ParameterRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if ParameterRef does not comply with the OpenAPI spec. +func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -765,11 +768,7 @@ func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -784,47 +783,52 @@ func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec. -func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if RequestBodyRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *RequestBodyRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec. +func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -903,11 +907,7 @@ func (x *ResponseRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -922,47 +922,52 @@ func (x *ResponseRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if ResponseRef does not comply with the OpenAPI spec. -func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if ResponseRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *ResponseRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if ResponseRef does not comply with the OpenAPI spec. +func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -990,6 +995,9 @@ type SchemaRef struct { Ref string Value *Schema extra []string + // sibling holds keyword siblings of a $ref (OAS 3.1 / JSON Schema 2020-12). + // It is populated during unmarshal and applied to Value after $ref resolution. + sibling *Schema refPath *url.URL } @@ -1041,11 +1049,23 @@ func (x *SchemaRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) + x.extra = componentNames(extra) + // OAS 3.1 / JSON Schema 2020-12: sibling keywords alongside $ref are valid + // and must be merged with the resolved reference. Parse the full object so + // the sibling fields are available after $ref resolution in resolveSchemaRef. + hasSiblings := false + for k := range extra { + if !strings.HasPrefix(k, "x-") { + hasSiblings = true + break + } + } + if hasSiblings { + var sibling Schema + if err := json.Unmarshal(data, &sibling); err == nil { + x.sibling = &sibling + } } - slices.Sort(x.extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -1060,47 +1080,54 @@ func (x *SchemaRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if SchemaRef does not comply with the OpenAPI spec. -func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if SchemaRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *SchemaRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + if !validationOpts.isOpenAPI31OrLater { + return newExtraSiblingFields(extras, x.Origin) + } + } + return nil +} + +// Validate returns an error if SchemaRef does not comply with the OpenAPI spec. +func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable @@ -1179,11 +1206,7 @@ func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) - } - slices.Sort(x.extra) + x.extra = componentNames(extra) for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -1198,47 +1221,52 @@ func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec. -func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if SecuritySchemeRef has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *SecuritySchemeRef) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + return newExtraSiblingFields(extras, x.Origin) + } + return nil +} + +// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec. +func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable diff --git a/openapi3/refs.tmpl b/openapi3/refs.tmpl index df33a6586..99dd94aa7 100644 --- a/openapi3/refs.tmpl +++ b/openapi3/refs.tmpl @@ -1,12 +1,10 @@ -// Code generated by go generate; DO NOT EDIT. +// Code generated by go generate using refs.tmpl; DO NOT EDIT refs.go. package {{ .Package }} import ( "context" "encoding/json" - "fmt" "net/url" - "slices" "strings" "github.com/go-openapi/jsonpointer" @@ -24,6 +22,11 @@ type {{ $type.Name }}Ref struct { Ref string Value *{{ $type.Name }} extra []string +{{- if eq $type.Name "Schema" }} + // sibling holds keyword siblings of a $ref (OAS 3.1 / JSON Schema 2020-12). + // It is populated during unmarshal and applied to Value after $ref resolution. + sibling *Schema +{{- end }} refPath *url.URL } @@ -75,11 +78,25 @@ func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error { x.Ref = refOnly.Ref x.Origin = refOnly.Origin if len(extra) != 0 { - x.extra = make([]string, 0, len(extra)) - for key := range extra { - x.extra = append(x.extra, key) + x.extra = componentNames(extra) +{{- if eq $type.Name "Schema" }} + // OAS 3.1 / JSON Schema 2020-12: sibling keywords alongside $ref are valid + // and must be merged with the resolved reference. Parse the full object so + // the sibling fields are available after $ref resolution in resolveSchemaRef. + hasSiblings := false + for k := range extra { + if !strings.HasPrefix(k, "x-") { + hasSiblings = true + break + } } - slices.Sort(x.extra) + if hasSiblings { + var sibling Schema + if err := json.Unmarshal(data, &sibling); err == nil { + x.sibling = &sibling + } + } +{{- end }} for k := range extra { if !strings.HasPrefix(k, "x-") { delete(extra, k) @@ -94,47 +111,58 @@ func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &x.Value) } -// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec. -func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) - exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited +// validateExtras returns an error if {{ $type.Name }}Ref has sibling fields +// alongside $ref that are not allowed by the validation options. +func (x *{{ $type.Name }}Ref) validateExtras(ctx context.Context) error { + validationOpts := getValidationOptions(ctx) var extras []string - if extra := x.extra; len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for _, ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } - } - // extras in the Extensions checked below + + allowed := validationOpts.extraSiblingFieldsAllowed + if allowed == nil { + allowed = make(map[string]struct{}) + } + for _, ex := range x.extra { + if _, ok := allowed[ex]; !ok { if _, ok := x.Extensions[ex]; !ok { extras = append(extras, ex) } + // extras in the Extensions checked below } } - if extra := x.Extensions; exProhibited && len(extra) != 0 { - allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - for ex := range extra { - if allowed != nil { - if _, ok := allowed[ex]; ok { - continue - } + if validationOpts.schemaExtensionsInRefProhibited { + for _, ex := range componentNames(x.Extensions) { + if _, ok := allowed[ex]; !ok { + extras = append(extras, ex) } - extras = append(extras, ex) + // extras in the Extensions checked below } } if len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) +{{- if eq $type.Name "Schema" }} + if !validationOpts.isOpenAPI31OrLater { + return newExtraSiblingFields(extras, x.Origin) + } +{{- else }} + return newExtraSiblingFields(extras, x.Origin) +{{- end }} + } + return nil +} + +// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec. +func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + if err := x.validateExtras(ctx); err != nil { + return err } if v := x.Value; v != nil { return v.Validate(ctx) } - return foundUnresolvedRef(x.Ref) + return newUnresolvedRef(x.Ref, x.Origin) } // JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable diff --git a/openapi3/refs_issue222_test.go b/openapi3/refs_issue222_test.go index 646d48751..f8044701a 100644 --- a/openapi3/refs_issue222_test.go +++ b/openapi3/refs_issue222_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue222(t *testing.T) { @@ -106,7 +108,7 @@ components: type: string `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.EqualError(t, err, `invalid response: value MUST be an object`) require.Nil(t, doc) diff --git a/openapi3/refs_issue247_test.go b/openapi3/refs_issue247_test.go index ae6d0c364..6bfabd7d6 100644 --- a/openapi3/refs_issue247_test.go +++ b/openapi3/refs_issue247_test.go @@ -1,4 +1,4 @@ -package openapi3 +package openapi3_test import ( "reflect" @@ -6,6 +6,8 @@ import ( "github.com/go-openapi/jsonpointer" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue247(t *testing.T) { @@ -111,7 +113,7 @@ components: format: int32 `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) require.NotNil(t, doc) @@ -128,93 +130,93 @@ components: v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Paths{}, v) - require.Equal(t, reflect.TypeOf(&Paths{}).Kind(), kind) + require.IsType(t, &openapi3.Paths{}, v) + require.Equal(t, reflect.TypeOf(&openapi3.Paths{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &PathItem{}, v) - require.Equal(t, reflect.TypeOf(&PathItem{}).Kind(), kind) + require.IsType(t, &openapi3.PathItem{}, v) + require.Equal(t, reflect.TypeOf(&openapi3.PathItem{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet/put") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Operation{}, v) - require.Equal(t, reflect.TypeOf(&Operation{}).Kind(), kind) + require.IsType(t, &openapi3.Operation{}, v) + require.Equal(t, reflect.TypeOf(&openapi3.Operation{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet/put/responses") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Responses{}, v) - require.Equal(t, reflect.TypeOf(&Responses{}).Kind(), kind) + require.IsType(t, &openapi3.Responses{}, v) + require.Equal(t, reflect.TypeOf(&openapi3.Responses{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Response{}, v) - require.Equal(t, reflect.TypeOf(&Response{}).Kind(), kind) + require.IsType(t, &openapi3.Response{}, v) + require.Equal(t, reflect.TypeOf(&openapi3.Response{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200/content") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, Content{}, v) - require.Equal(t, reflect.TypeOf(Content{}).Kind(), kind) + require.IsType(t, openapi3.Content{}, v) + require.Equal(t, reflect.TypeOf(openapi3.Content{}).Kind(), kind) ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200/content/application~1json/schema") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Ref{}, v) + require.IsType(t, &openapi3.Ref{}, v) require.Equal(t, reflect.Ptr, kind) - require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref) + require.Equal(t, "#/components/schemas/Pet", v.(*openapi3.Ref).Ref) ptr, err = jsonpointer.New("/components/schemas/Pets/items") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Ref{}, v) + require.IsType(t, &openapi3.Ref{}, v) require.Equal(t, reflect.Ptr, kind) - require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref) + require.Equal(t, "#/components/schemas/Pet", v.(*openapi3.Ref).Ref) ptr, err = jsonpointer.New("/components/schemas/Error/properties/code") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Schema{}, v) + require.IsType(t, &openapi3.Schema{}, v) require.Equal(t, reflect.Ptr, kind) - require.Equal(t, &Types{"integer"}, v.(*Schema).Type) + require.Equal(t, &openapi3.Types{"integer"}, v.(*openapi3.Schema).Type) ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/0") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Schema{}, v) + require.IsType(t, &openapi3.Schema{}, v) require.Equal(t, reflect.Ptr, kind) - require.Equal(t, &Types{"string"}, v.(*Schema).Type) + require.Equal(t, &openapi3.Types{"string"}, v.(*openapi3.Schema).Type) ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/1") require.NoError(t, err) v, kind, err = ptr.Get(doc) require.NoError(t, err) require.NotNil(t, v) - require.IsType(t, &Schema{}, v) + require.IsType(t, &openapi3.Schema{}, v) require.Equal(t, reflect.Ptr, kind) - require.Equal(t, &Types{"integer"}, v.(*Schema).Type) + require.Equal(t, &openapi3.Types{"integer"}, v.(*openapi3.Schema).Type) ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/5") require.NoError(t, err) diff --git a/openapi3/refs_test.go b/openapi3/refs_test.go index 89ad64e1f..2e79086cb 100644 --- a/openapi3/refs_test.go +++ b/openapi3/refs_test.go @@ -1,20 +1,21 @@ // Code generated by go generate; DO NOT EDIT. -package openapi3 +package openapi3_test import ( - "context" "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestCallbackRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := CallbackRef{} + ref := openapi3.CallbackRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -26,13 +27,13 @@ func TestCallbackRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -46,7 +47,7 @@ func TestCallbackRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Callback{Extensions: map[string]any{}} + ref.Value = &openapi3.Callback{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -60,7 +61,7 @@ func TestExampleRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := ExampleRef{} + ref := openapi3.ExampleRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -72,13 +73,13 @@ func TestExampleRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -92,7 +93,7 @@ func TestExampleRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Example{Extensions: map[string]any{}} + ref.Value = &openapi3.Example{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -106,7 +107,7 @@ func TestHeaderRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := HeaderRef{} + ref := openapi3.HeaderRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -118,13 +119,13 @@ func TestHeaderRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -144,7 +145,7 @@ func TestLinkRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := LinkRef{} + ref := openapi3.LinkRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -156,13 +157,13 @@ func TestLinkRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -176,7 +177,7 @@ func TestLinkRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Link{Extensions: map[string]any{}} + ref.Value = &openapi3.Link{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -190,7 +191,7 @@ func TestParameterRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := ParameterRef{} + ref := openapi3.ParameterRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -202,13 +203,13 @@ func TestParameterRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -222,7 +223,7 @@ func TestParameterRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Parameter{Extensions: map[string]any{}} + ref.Value = &openapi3.Parameter{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -236,7 +237,7 @@ func TestRequestBodyRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := RequestBodyRef{} + ref := openapi3.RequestBodyRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -248,13 +249,13 @@ func TestRequestBodyRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -268,7 +269,7 @@ func TestRequestBodyRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &RequestBody{Extensions: map[string]any{}} + ref.Value = &openapi3.RequestBody{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -282,7 +283,7 @@ func TestResponseRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := ResponseRef{} + ref := openapi3.ResponseRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -294,13 +295,13 @@ func TestResponseRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -314,7 +315,7 @@ func TestResponseRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Response{Extensions: map[string]any{}} + ref.Value = &openapi3.Response{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -328,7 +329,7 @@ func TestSchemaRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := SchemaRef{} + ref := openapi3.SchemaRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -340,13 +341,13 @@ func TestSchemaRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -360,7 +361,7 @@ func TestSchemaRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &Schema{Extensions: map[string]any{}} + ref.Value = &openapi3.Schema{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. @@ -374,7 +375,7 @@ func TestSecuritySchemeRef_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := SecuritySchemeRef{} + ref := openapi3.SecuritySchemeRef{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -386,13 +387,13 @@ func TestSecuritySchemeRef_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -406,7 +407,7 @@ func TestSecuritySchemeRef_Extensions(t *testing.T) { assert.Error(t, err) t.Run("extentions in value", func(t *testing.T) { - ref.Value = &SecurityScheme{Extensions: map[string]any{}} + ref.Value = &openapi3.SecurityScheme{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. diff --git a/openapi3/refs_test.tmpl b/openapi3/refs_test.tmpl index a11e1769d..4aabdc4e9 100644 --- a/openapi3/refs_test.tmpl +++ b/openapi3/refs_test.tmpl @@ -1,20 +1,21 @@ // Code generated by go generate; DO NOT EDIT. -package {{ .Package }} +package {{ .Package }}_test import ( - "context" "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) {{ range $type := .Types }} func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) expectMarshalJson := []byte(`{"$ref":"#/components/schemas/Pet","x-order":1}`) - ref := {{ $type.Name }}Ref{} + ref := openapi3.{{ $type.Name }}Ref{} err := json.Unmarshal(data, &ref) assert.NoError(t, err) @@ -26,13 +27,13 @@ func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { assert.Nil(t, ref.Extensions["something"]) // validation - err = ref.Validate(context.Background()) + err = ref.Validate(t.Context()) require.EqualError(t, err, "extra sibling fields: [something]") - err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + err = ref.Validate(t.Context(), openapi3.ProhibitExtensionsWithRef()) require.EqualError(t, err, "extra sibling fields: [something x-order]") - err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + err = ref.Validate(t.Context(), openapi3.AllowExtraSiblingFields("something")) assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined // Verify round trip JSON @@ -46,7 +47,7 @@ func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { assert.Error(t, err) {{ if ne $type.Name "Header" }} t.Run("extentions in value", func(t *testing.T) { - ref.Value = &{{ $type.Name }}{Extensions: map[string]any{}} + ref.Value = &openapi3.{{ $type.Name }}{Extensions: map[string]any{}} ref.Value.Extensions["x-order"] = 2.0 // prefers the value next to the \$ref over the one in the \$ref. diff --git a/openapi3/request_body.go b/openapi3/request_body.go index 3e18890c9..e8c52a1cb 100644 --- a/openapi3/request_body.go +++ b/openapi3/request_body.go @@ -3,7 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" + "maps" ) // RequestBody is specified by OpenAPI/Swagger 3.0 standard. @@ -86,9 +86,7 @@ func (requestBody RequestBody) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of RequestBody. func (requestBody RequestBody) MarshalYAML() (any, error) { m := make(map[string]any, 3+len(requestBody.Extensions)) - for k, v := range requestBody.Extensions { - m[k] = v - } + maps.Copy(m, requestBody.Extensions) if x := requestBody.Description; x != "" { m["description"] = requestBody.Description } @@ -124,7 +122,7 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation ctx = WithValidationOptions(ctx, opts...) if requestBody.Content == nil { - return errors.New("content of the request body is required") + return newRequestBodyContentRequired(requestBody.Origin) } if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { @@ -135,7 +133,7 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation return err } - return validateExtensions(ctx, requestBody.Extensions) + return validateExtensions(ctx, requestBody.Extensions, requestBody.Origin) } // UnmarshalJSON sets RequestBodies to a copy of data. diff --git a/openapi3/response.go b/openapi3/response.go index 0a83c83dc..ddc3a7b5d 100644 --- a/openapi3/response.go +++ b/openapi3/response.go @@ -3,8 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "errors" - "slices" + "maps" "strconv" ) @@ -76,24 +75,24 @@ func (responses *Responses) Status(status int) *ResponseRef { // Validate returns an error if Responses does not comply with the OpenAPI spec. func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if responses.Len() == 0 { - return errors.New("the responses object MUST contain at least one response code") + if err := me.emit(newResponsesNonEmptyRequired(responses.Origin)); err != nil { + return err + } + // Fall through so validateExtensions still runs and any extension + // errors aggregate with the empty-responses finding under multi mode. } - keys := make([]string, 0, responses.Len()) - for key := range responses.Map() { - keys = append(keys, key) - } - slices.Sort(keys) - for _, key := range keys { + for _, key := range responses.Keys() { v := responses.Value(key) - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } - return validateExtensions(ctx, responses.Extensions) + return me.finalize(validateExtensions(ctx, responses.Extensions, responses.Origin)) } // Response is specified by OpenAPI/Swagger 3.0 standard. @@ -144,9 +143,7 @@ func (response Response) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Response. func (response Response) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(response.Extensions)) - for k, v := range response.Extensions { - m[k] = v - } + maps.Copy(m, response.Extensions) if x := response.Description; x != nil { m["description"] = x } @@ -186,7 +183,7 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption ctx = WithValidationOptions(ctx, opts...) if response.Description == nil { - return errors.New("a short description of the response is required") + return newResponseDescriptionRequired(response.Origin) } if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true @@ -198,31 +195,21 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption } } - headers := make([]string, 0, len(response.Headers)) - for name := range response.Headers { - headers = append(headers, name) - } - slices.Sort(headers) - for _, name := range headers { + for _, name := range componentNames(response.Headers) { header := response.Headers[name] if err := header.Validate(ctx); err != nil { return err } } - links := make([]string, 0, len(response.Links)) - for name := range response.Links { - links = append(links, name) - } - slices.Sort(links) - for _, name := range links { + for _, name := range componentNames(response.Links) { link := response.Links[name] if err := link.Validate(ctx); err != nil { return err } } - return validateExtensions(ctx, response.Extensions) + return validateExtensions(ctx, response.Extensions, response.Origin) } // UnmarshalJSON sets ResponseBodies to a copy of data. diff --git a/openapi3/response_issue224_test.go b/openapi3/response_issue224_test.go index 265f88a29..185291d89 100644 --- a/openapi3/response_issue224_test.go +++ b/openapi3/response_issue224_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -460,6 +459,6 @@ func TestEmptyResponsesAreInvalid(t *testing.T) { require.Equal(t, "See AsyncAPI example", doc.ExternalDocs.Description) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.EqualError(t, err, `invalid paths: invalid path /pet: invalid operation POST: the responses object MUST contain at least one response code`) } diff --git a/openapi3/schema.go b/openapi3/schema.go index 986734e1d..e44fe811a 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "math" "math/big" "reflect" @@ -80,6 +81,7 @@ func (s SchemaRefs) JSONLookup(token string) (any, error) { // Schema is specified by OpenAPI/Swagger 3.0 standard. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object +// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object type Schema struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -100,8 +102,8 @@ type Schema struct { // Array-related, here for struct compactness UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` // Number-related, here for struct compactness - ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` - ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + ExclusiveMin ExclusiveBound `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` // Number for v3.1+ otherwise boolean + ExclusiveMax ExclusiveBound `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` // Number for v3.1+ otherwise boolean // Properties Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` @@ -132,14 +134,82 @@ type Schema struct { MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` AdditionalProperties AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` -} + Const any `json:"const,omitempty" yaml:"const,omitempty"` // OpenAPI >=3.1 + Examples []any `json:"examples,omitempty" yaml:"examples,omitempty"` // OpenAPI >=3.1 + PrefixItems SchemaRefs `json:"prefixItems,omitempty" yaml:"prefixItems,omitempty"` // OpenAPI >=3.1 + Contains *SchemaRef `json:"contains,omitempty" yaml:"contains,omitempty"` // OpenAPI >=3.1 + MinContains *uint64 `json:"minContains,omitempty" yaml:"minContains,omitempty"` // OpenAPI >=3.1 + MaxContains *uint64 `json:"maxContains,omitempty" yaml:"maxContains,omitempty"` // OpenAPI >=3.1 + PatternProperties Schemas `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` // OpenAPI >=3.1 + DependentSchemas Schemas `json:"dependentSchemas,omitempty" yaml:"dependentSchemas,omitempty"` // OpenAPI >=3.1 + PropertyNames *SchemaRef `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` // OpenAPI >=3.1 + UnevaluatedItems BoolSchema `json:"unevaluatedItems,omitempty" yaml:"unevaluatedItems,omitempty"` // OpenAPI >=3.1 + UnevaluatedProperties BoolSchema `json:"unevaluatedProperties,omitempty" yaml:"unevaluatedProperties,omitempty"` // OpenAPI >=3.1 + + If *SchemaRef `json:"if,omitempty" yaml:"if,omitempty"` // OpenAPI >=3.1 + Then *SchemaRef `json:"then,omitempty" yaml:"then,omitempty"` // OpenAPI >=3.1 + Else *SchemaRef `json:"else,omitempty" yaml:"else,omitempty"` // OpenAPI >=3.1 + + DependentRequired map[string][]string `json:"dependentRequired,omitempty" yaml:"dependentRequired,omitempty"` // OpenAPI >=3.1 + + Defs Schemas `json:"$defs,omitempty" yaml:"$defs,omitempty"` // OpenAPI >=3.1 + SchemaDialect string `json:"$schema,omitempty" yaml:"$schema,omitempty"` // OpenAPI >=3.1 + Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` // OpenAPI >=3.1 + + SchemaID string `json:"$id,omitempty" yaml:"$id,omitempty"` // OpenAPI >=3.1 + Anchor string `json:"$anchor,omitempty" yaml:"$anchor,omitempty"` // OpenAPI >=3.1 + DynamicRef string `json:"$dynamicRef,omitempty" yaml:"$dynamicRef,omitempty"` // OpenAPI >=3.1 + DynamicAnchor string `json:"$dynamicAnchor,omitempty" yaml:"$dynamicAnchor,omitempty"` // OpenAPI >=3.1 + + ContentMediaType string `json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"` // OpenAPI >=3.1 + ContentEncoding string `json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"` // OpenAPI >=3.1 + ContentSchema *SchemaRef `json:"contentSchema,omitempty" yaml:"contentSchema,omitempty"` // OpenAPI >=3.1 +} + +// Types represents the type(s) of a schema. +// +// In OpenAPI 3.0, this is typically a single type (e.g., "string"). +// In OpenAPI 3.1, it can be an array of types (e.g., ["string", "null"]). +// +// Serialization behavior: +// - Single type: serializes as a string (e.g., "string") +// - Multiple types: serializes as an array (e.g., ["string", "null"]) +// - Accepts both string and array formats when unmarshaling +// +// Example OpenAPI 3.0 (single type): +// +// schema := &Schema{Type: &Types{"string"}} +// // JSON: {"type": "string"} +// +// Example OpenAPI 3.1 (type array): +// +// schema := &Schema{Type: &Types{"string", "null"}} +// // JSON: {"type": ["string", "null"]} type Types []string +// Is returns true if the schema has exactly one type and it matches the given type. +// This is useful for OpenAPI 3.0 style single-type checks. +// +// Example: +// +// types := &Types{"string"} +// types.Is("string") // true +// types.Is("number") // false +// +// types = &Types{"string", "null"} +// types.Is("string") // false (multiple types) func (types *Types) Is(typ string) bool { return types != nil && len(*types) == 1 && (*types)[0] == typ } +// Slice returns the types as a string slice. +// Returns nil if types is nil. +// +// Example: +// +// types := &Types{"string", "null"} +// slice := types.Slice() // []string{"string", "null"} func (types *Types) Slice() []string { if types == nil { return nil @@ -147,19 +217,33 @@ func (types *Types) Slice() []string { return *types } +// Includes returns true if the given type is included in the type array. +// Returns false if types is nil. +// +// Example: +// +// types := &Types{"string", "null"} +// types.Includes("string") // true +// types.Includes("null") // true +// types.Includes("number") // false func (pTypes *Types) Includes(typ string) bool { if pTypes == nil { return false } - types := *pTypes - for _, candidate := range types { - if candidate == typ { - return true - } - } - return false + return slices.Contains(*pTypes, typ) } +// Permits returns true if the given type is permitted. +// Returns true if types is nil (any type allowed), otherwise checks if the type is included. +// +// Example: +// +// var nilTypes *Types +// nilTypes.Permits("anything") // true (nil permits everything) +// +// types := &Types{"string"} +// types.Permits("string") // true +// types.Permits("number") // false func (types *Types) Permits(typ string) bool { if types == nil { return true @@ -167,6 +251,64 @@ func (types *Types) Permits(typ string) bool { return types.Includes(typ) } +// IncludesNull returns true if the type array includes "null". +// This is useful for OpenAPI 3.1 where null is a first-class type. +// +// Example: +// +// types := &Types{"string", "null"} +// types.IncludesNull() // true +// +// types = &Types{"string"} +// types.IncludesNull() // false +func (types *Types) IncludesNull() bool { + return types.Includes(TypeNull) +} + +// IsMultiple returns true if multiple types are specified. +// This is an OpenAPI 3.1 feature that enables type arrays. +// +// Example: +// +// types := &Types{"string"} +// types.IsMultiple() // false +// +// types = &Types{"string", "null"} +// types.IsMultiple() // true +func (types *Types) IsMultiple() bool { + return types != nil && len(*types) > 1 +} + +// IsSingle returns true if exactly one type is specified. +// +// Example: +// +// types := &Types{"string"} +// types.IsSingle() // true +// +// types = &Types{"string", "null"} +// types.IsSingle() // false +func (types *Types) IsSingle() bool { + return types != nil && len(*types) == 1 +} + +// IsEmpty returns true if no types are specified (nil or empty array). +// When a schema has no type specified, it permits any type. +// +// Example: +// +// var nilTypes *Types +// nilTypes.IsEmpty() // true +// +// types := &Types{} +// types.IsEmpty() // true +// +// types = &Types{"string"} +// types.IsEmpty() // false +func (types *Types) IsEmpty() bool { + return types == nil || len(*types) == 0 +} + func (pTypes *Types) MarshalJSON() ([]byte, error) { x, err := pTypes.MarshalYAML() if err != nil { @@ -203,36 +345,41 @@ func (types *Types) UnmarshalJSON(data []byte) error { return nil } -type AdditionalProperties struct { +// BoolSchema represents a JSON Schema keyword that can be either a boolean or a schema object. +// Used for additionalProperties, unevaluatedProperties, and unevaluatedItems. +type BoolSchema struct { Has *bool Schema *SchemaRef } -// MarshalYAML returns the YAML encoding of AdditionalProperties. -func (addProps AdditionalProperties) MarshalYAML() (any, error) { - if x := addProps.Has; x != nil { +// AdditionalProperties is a type alias for BoolSchema, kept for backward compatibility. +type AdditionalProperties = BoolSchema + +// MarshalYAML returns the YAML encoding of BoolSchema. +func (bs BoolSchema) MarshalYAML() (any, error) { + if x := bs.Has; x != nil { if *x { return true, nil } return false, nil } - if x := addProps.Schema; x != nil { + if x := bs.Schema; x != nil { return x.MarshalYAML() } return nil, nil } -// MarshalJSON returns the JSON encoding of AdditionalProperties. -func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) { - x, err := addProps.MarshalYAML() +// MarshalJSON returns the JSON encoding of BoolSchema. +func (bs BoolSchema) MarshalJSON() ([]byte, error) { + x, err := bs.MarshalYAML() if err != nil { return nil, err } return json.Marshal(x) } -// UnmarshalJSON sets AdditionalProperties to a copy of data. -func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { +// UnmarshalJSON sets BoolSchema to a copy of data. +func (bs *BoolSchema) UnmarshalJSON(data []byte) error { var x any if err := json.Unmarshal(data, &x); err != nil { return unmarshalError(err) @@ -240,19 +387,79 @@ func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { switch y := x.(type) { case nil: case bool: - addProps.Has = &y + bs.Has = &y case map[string]any: if len(y) == 0 { - addProps.Schema = &SchemaRef{Value: &Schema{}} + bs.Schema = &SchemaRef{Value: &Schema{}} } else { buf := new(bytes.Buffer) _ = json.NewEncoder(buf).Encode(y) - if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil { + if err := json.NewDecoder(buf).Decode(&bs.Schema); err != nil { return err } } default: - return errors.New("cannot unmarshal additionalProperties: value must be either a schema object or a boolean") + return errors.New("cannot unmarshal: value must be either a schema object or a boolean") + } + return nil +} + +// ExclusiveBound represents exclusiveMinimum/exclusiveMaximum which changed type between OpenAPI versions. +// In OpenAPI 3.0 (JSON Schema draft-04): boolean that modifies minimum/maximum +// In OpenAPI 3.1 (JSON Schema 2020-12): number representing the actual exclusive bound +type ExclusiveBound struct { + Bool *bool // For OpenAPI 3.0 style (modifier for min/max) + Value *float64 // For OpenAPI 3.1 style (actual bound value) +} + +// IsSet returns true if either Bool or Value is set. +func (eb ExclusiveBound) IsSet() bool { + return eb.Bool != nil || eb.Value != nil +} + +// IsTrue returns true if the bound is set as a boolean true (OpenAPI 3.0 style). +func (eb ExclusiveBound) IsTrue() bool { + return eb.Bool != nil && *eb.Bool +} + +// MarshalYAML returns the YAML encoding of ExclusiveBound. +func (eb ExclusiveBound) MarshalYAML() (any, error) { + if eb.Value != nil { + return *eb.Value, nil + } + if eb.Bool != nil { + return *eb.Bool, nil + } + return nil, nil +} + +// MarshalJSON returns the JSON encoding of ExclusiveBound. +func (eb ExclusiveBound) MarshalJSON() ([]byte, error) { + x, err := eb.MarshalYAML() + if err != nil { + return nil, err + } + if x == nil { + return nil, nil + } + return json.Marshal(x) +} + +// UnmarshalJSON sets ExclusiveBound to a copy of data. +func (eb *ExclusiveBound) UnmarshalJSON(data []byte) error { + var x any + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + switch y := x.(type) { + case nil: + // nothing to do + case bool: + eb.Bool = &y + case float64: + eb.Value = &y + default: + return errors.New("cannot unmarshal exclusiveMinimum/exclusiveMaximum: value must be either a number or a boolean") } return nil } @@ -275,10 +482,8 @@ func (schema Schema) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Schema. func (schema Schema) MarshalYAML() (any, error) { - m := make(map[string]any, 36+len(schema.Extensions)) - for k, v := range schema.Extensions { - m[k] = v - } + m := make(map[string]any, 61+len(schema.Extensions)) + maps.Copy(m, schema.Extensions) if x := schema.OneOf; len(x) != 0 { m["oneOf"] = x @@ -322,10 +527,10 @@ func (schema Schema) MarshalYAML() (any, error) { m["uniqueItems"] = x } // Number-related - if x := schema.ExclusiveMin; x { + if x := schema.ExclusiveMin; x.IsSet() { m["exclusiveMinimum"] = x } - if x := schema.ExclusiveMax; x { + if x := schema.ExclusiveMax; x.IsSet() { m["exclusiveMaximum"] = x } // Properties @@ -401,6 +606,83 @@ func (schema Schema) MarshalYAML() (any, error) { m["discriminator"] = x } + // OpenAPI 3.1 / JSON Schema 2020-12 fields + if x := schema.Const; x != nil { + m["const"] = x + } + if x := schema.Examples; len(x) != 0 { + m["examples"] = x + } + if x := schema.PrefixItems; len(x) != 0 { + m["prefixItems"] = x + } + if x := schema.Contains; x != nil { + m["contains"] = x + } + if x := schema.MinContains; x != nil { + m["minContains"] = x + } + if x := schema.MaxContains; x != nil { + m["maxContains"] = x + } + if x := schema.PatternProperties; len(x) != 0 { + m["patternProperties"] = x + } + if x := schema.DependentSchemas; len(x) != 0 { + m["dependentSchemas"] = x + } + if x := schema.PropertyNames; x != nil { + m["propertyNames"] = x + } + if x := schema.UnevaluatedItems; x.Has != nil || x.Schema != nil { + m["unevaluatedItems"] = &x + } + if x := schema.UnevaluatedProperties; x.Has != nil || x.Schema != nil { + m["unevaluatedProperties"] = &x + } + if x := schema.If; x != nil { + m["if"] = x + } + if x := schema.Then; x != nil { + m["then"] = x + } + if x := schema.Else; x != nil { + m["else"] = x + } + if x := schema.DependentRequired; len(x) != 0 { + m["dependentRequired"] = x + } + if x := schema.Defs; len(x) != 0 { + m["$defs"] = x + } + if x := schema.SchemaDialect; x != "" { + m["$schema"] = x + } + if x := schema.Comment; x != "" { + m["$comment"] = x + } + if x := schema.SchemaID; x != "" { + m["$id"] = x + } + if x := schema.Anchor; x != "" { + m["$anchor"] = x + } + if x := schema.DynamicRef; x != "" { + m["$dynamicRef"] = x + } + if x := schema.DynamicAnchor; x != "" { + m["$dynamicAnchor"] = x + } + if x := schema.ContentMediaType; x != "" { + m["contentMediaType"] = x + } + if x := schema.ContentEncoding; x != "" { + m["contentEncoding"] = x + } + if x := schema.ContentSchema; x != nil { + m["contentSchema"] = x + } + return m, nil } @@ -462,18 +744,38 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { delete(x.Extensions, "additionalProperties") delete(x.Extensions, "discriminator") + // OpenAPI 3.1 / JSON Schema 2020-12 fields + delete(x.Extensions, "const") + delete(x.Extensions, "examples") + delete(x.Extensions, "prefixItems") + delete(x.Extensions, "contains") + delete(x.Extensions, "minContains") + delete(x.Extensions, "maxContains") + delete(x.Extensions, "patternProperties") + delete(x.Extensions, "dependentSchemas") + delete(x.Extensions, "propertyNames") + delete(x.Extensions, "unevaluatedItems") + delete(x.Extensions, "unevaluatedProperties") + delete(x.Extensions, "if") + delete(x.Extensions, "then") + delete(x.Extensions, "else") + delete(x.Extensions, "dependentRequired") + delete(x.Extensions, "$defs") + delete(x.Extensions, "$schema") + delete(x.Extensions, "$comment") + delete(x.Extensions, "$id") + delete(x.Extensions, "$anchor") + delete(x.Extensions, "$dynamicRef") + delete(x.Extensions, "$dynamicAnchor") + delete(x.Extensions, "contentMediaType") + delete(x.Extensions, "contentEncoding") + delete(x.Extensions, "contentSchema") + if len(x.Extensions) == 0 { x.Extensions = nil } *schema = Schema(x) - - if schema.Format == "date" { - // This is a fix for: https://github.com/getkin/kin-openapi/issues/697 - if eg, ok := schema.Example.(string); ok { - schema.Example = strings.TrimSuffix(eg, "T00:00:00Z") - } - } return nil } @@ -570,6 +872,104 @@ func (schema Schema) JSONLookup(token string) (any, error) { return schema.MaxProps, nil case "discriminator": return schema.Discriminator, nil + + // OpenAPI 3.1 / JSON Schema 2020-12 fields + case "const": + return schema.Const, nil + case "examples": + return schema.Examples, nil + case "prefixItems": + return schema.PrefixItems, nil + case "contains": + if schema.Contains != nil { + if schema.Contains.Ref != "" { + return &Ref{Ref: schema.Contains.Ref}, nil + } + return schema.Contains.Value, nil + } + case "minContains": + return schema.MinContains, nil + case "maxContains": + return schema.MaxContains, nil + case "patternProperties": + return schema.PatternProperties, nil + case "dependentSchemas": + return schema.DependentSchemas, nil + case "propertyNames": + if schema.PropertyNames != nil { + if schema.PropertyNames.Ref != "" { + return &Ref{Ref: schema.PropertyNames.Ref}, nil + } + return schema.PropertyNames.Value, nil + } + case "unevaluatedItems": + if ui := schema.UnevaluatedItems.Has; ui != nil { + return *ui, nil + } + if ui := schema.UnevaluatedItems.Schema; ui != nil { + if ui.Ref != "" { + return &Ref{Ref: ui.Ref}, nil + } + return ui.Value, nil + } + case "unevaluatedProperties": + if up := schema.UnevaluatedProperties.Has; up != nil { + return *up, nil + } + if up := schema.UnevaluatedProperties.Schema; up != nil { + if up.Ref != "" { + return &Ref{Ref: up.Ref}, nil + } + return up.Value, nil + } + case "if": + if schema.If != nil { + if schema.If.Ref != "" { + return &Ref{Ref: schema.If.Ref}, nil + } + return schema.If.Value, nil + } + case "then": + if schema.Then != nil { + if schema.Then.Ref != "" { + return &Ref{Ref: schema.Then.Ref}, nil + } + return schema.Then.Value, nil + } + case "else": + if schema.Else != nil { + if schema.Else.Ref != "" { + return &Ref{Ref: schema.Else.Ref}, nil + } + return schema.Else.Value, nil + } + case "dependentRequired": + return schema.DependentRequired, nil + case "$defs": + return schema.Defs, nil + case "$schema": + return schema.SchemaDialect, nil + case "$comment": + return schema.Comment, nil + case "$id": + return schema.SchemaID, nil + case "$anchor": + return schema.Anchor, nil + case "$dynamicRef": + return schema.DynamicRef, nil + case "$dynamicAnchor": + return schema.DynamicAnchor, nil + case "contentMediaType": + return schema.ContentMediaType, nil + case "contentEncoding": + return schema.ContentEncoding, nil + case "contentSchema": + if schema.ContentSchema != nil { + if schema.ContentSchema.Ref != "" { + return &Ref{Ref: schema.ContentSchema.Ref}, nil + } + return schema.ContentSchema.Value, nil + } } v, _, err := jsonpointer.GetForToken(schema.Extensions, token) @@ -699,13 +1099,27 @@ func (schema *Schema) WithMax(value float64) *Schema { return schema } +// WithExclusiveMin sets exclusiveMinimum as a boolean (OpenAPI 3.0 style). func (schema *Schema) WithExclusiveMin(value bool) *Schema { - schema.ExclusiveMin = value + schema.ExclusiveMin = ExclusiveBound{Bool: &value} return schema } +// WithExclusiveMax sets exclusiveMaximum as a boolean (OpenAPI 3.0 style). func (schema *Schema) WithExclusiveMax(value bool) *Schema { - schema.ExclusiveMax = value + schema.ExclusiveMax = ExclusiveBound{Bool: &value} + return schema +} + +// WithExclusiveMinValue sets exclusiveMinimum as a number (OpenAPI 3.1 style). +func (schema *Schema) WithExclusiveMinValue(value float64) *Schema { + schema.ExclusiveMin = ExclusiveBound{Value: &value} + return schema +} + +// WithExclusiveMaxValue sets exclusiveMaximum as a number (OpenAPI 3.1 style). +func (schema *Schema) WithExclusiveMaxValue(value float64) *Schema { + schema.ExclusiveMax = ExclusiveBound{Value: &value} return schema } @@ -799,12 +1213,10 @@ func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema } func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { - properties := schema.Properties - if properties == nil { - properties = make(Schemas) - schema.Properties = properties + if schema.Properties == nil { + schema.Properties = make(Schemas) } - properties[name] = ref + schema.Properties[name] = ref return schema } @@ -855,19 +1267,20 @@ func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { } func (schema *Schema) PermitsNull() bool { - return schema.Nullable || schema.Type.Includes("null") + return schema.Nullable || schema.Type.IncludesNull() } // IsEmpty tells whether schema is equivalent to the empty schema `{}`. func (schema *Schema) IsEmpty() bool { if schema.Type != nil || schema.Format != "" || len(schema.Enum) != 0 || - schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax || + schema.UniqueItems || schema.ExclusiveMin.IsSet() || schema.ExclusiveMax.IsSet() || schema.Nullable || schema.ReadOnly || schema.WriteOnly || schema.AllowEmptyValue || schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil || schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" || schema.MinItems != 0 || schema.MaxItems != nil || len(schema.Required) != 0 || - schema.MinProps != 0 || schema.MaxProps != nil { + schema.MinProps != 0 || schema.MaxProps != nil || + schema.Const != nil { return false } if n := schema.Not; n != nil && n.Value != nil && !n.Value.IsEmpty() { @@ -882,11 +1295,50 @@ func (schema *Schema) IsEmpty() bool { if items := schema.Items; items != nil && items.Value != nil && !items.Value.IsEmpty() { return false } + for _, s := range schema.PrefixItems { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + if c := schema.Contains; c != nil && c.Value != nil && !c.Value.IsEmpty() { + return false + } + if schema.MinContains != nil || schema.MaxContains != nil { + return false + } for _, s := range schema.Properties { if ss := s.Value; ss != nil && !ss.IsEmpty() { return false } } + for _, s := range schema.PatternProperties { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.DependentSchemas { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + if pn := schema.PropertyNames; pn != nil && pn.Value != nil && !pn.Value.IsEmpty() { + return false + } + if ui := schema.UnevaluatedItems.Schema; ui != nil && ui.Value != nil && !ui.Value.IsEmpty() { + return false + } + if uih := schema.UnevaluatedItems.Has; uih != nil && !*uih { + return false + } + if up := schema.UnevaluatedProperties.Schema; up != nil && up.Value != nil && !up.Value.IsEmpty() { + return false + } + if uph := schema.UnevaluatedProperties.Has; uph != nil && !*uph { + return false + } + if len(schema.Examples) != 0 { + return false + } for _, s := range schema.OneOf { if ss := s.Value; ss != nil && !ss.IsEmpty() { return false @@ -902,35 +1354,204 @@ func (schema *Schema) IsEmpty() bool { return false } } + if f := schema.If; f != nil && f.Value != nil && !f.Value.IsEmpty() { + return false + } + if t := schema.Then; t != nil && t.Value != nil && !t.Value.IsEmpty() { + return false + } + if e := schema.Else; e != nil && e.Value != nil && !e.Value.IsEmpty() { + return false + } + if len(schema.DependentRequired) != 0 { + return false + } + if len(schema.Defs) != 0 { + return false + } + if schema.SchemaDialect != "" || schema.Comment != "" { + return false + } + if schema.SchemaID != "" || schema.Anchor != "" || schema.DynamicRef != "" || schema.DynamicAnchor != "" { + return false + } + if schema.ContentMediaType != "" || schema.ContentEncoding != "" { + return false + } + if cs := schema.ContentSchema; cs != nil && cs.Value != nil && !cs.Value.IsEmpty() { + return false + } return true } // Validate returns an error if Schema does not comply with the OpenAPI spec. func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error { + // Apply document-level validation options to the context ctx = WithValidationOptions(ctx, opts...) + + // Perform schema validation with the options in context _, err := schema.validate(ctx, []*Schema{}) return err } // returns the updated stack and an error if Schema does not comply with the OpenAPI spec. func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) { + if slices.Contains(stack, schema) { + return stack, nil + } + validationOpts := getValidationOptions(ctx) - for _, existing := range stack { - if existing == schema { - return stack, nil - } - } stack = append(stack, schema) if schema.ReadOnly && schema.WriteOnly { - return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true") + return stack, newSchemaReadOnlyWriteOnlyExclusive(schema.Origin) + } + + // Reject fields that only exist in OAS 3.1 / JSON Schema 2020-12 when the + // document is OAS 3.0. Fields explicitly allowed via AllowExtraSiblingFields + // are skipped (opt-in escape hatch for 3.0 docs that reference external + // JSON Schema documents). Once 3.0 moves to its own schema validator this + // block becomes a no-op. + if !validationOpts.isOpenAPI31OrLater { + allowed := validationOpts.extraSiblingFieldsAllowed + reject := func(field string) error { + if _, ok := allowed[field]; ok { + return nil + } + return errFieldFor31Plus(field, schema.Origin) + } + if schema.Const != nil { + if err := reject("const"); err != nil { + return stack, err + } + } + if len(schema.Examples) != 0 { + if err := reject("examples"); err != nil { + return stack, err + } + } + if len(schema.PrefixItems) != 0 { + if err := reject("prefixItems"); err != nil { + return stack, err + } + } + if schema.Contains != nil { + if err := reject("contains"); err != nil { + return stack, err + } + } + if schema.MinContains != nil { + if err := reject("minContains"); err != nil { + return stack, err + } + } + if schema.MaxContains != nil { + if err := reject("maxContains"); err != nil { + return stack, err + } + } + if len(schema.PatternProperties) != 0 { + if err := reject("patternProperties"); err != nil { + return stack, err + } + } + if len(schema.DependentSchemas) != 0 { + if err := reject("dependentSchemas"); err != nil { + return stack, err + } + } + if schema.PropertyNames != nil { + if err := reject("propertyNames"); err != nil { + return stack, err + } + } + if schema.UnevaluatedItems.Has != nil || schema.UnevaluatedItems.Schema != nil { + if err := reject("unevaluatedItems"); err != nil { + return stack, err + } + } + if schema.UnevaluatedProperties.Has != nil || schema.UnevaluatedProperties.Schema != nil { + if err := reject("unevaluatedProperties"); err != nil { + return stack, err + } + } + if schema.If != nil { + if err := reject("if"); err != nil { + return stack, err + } + } + if schema.Then != nil { + if err := reject("then"); err != nil { + return stack, err + } + } + if schema.Else != nil { + if err := reject("else"); err != nil { + return stack, err + } + } + if len(schema.DependentRequired) != 0 { + if err := reject("dependentRequired"); err != nil { + return stack, err + } + } + if len(schema.Defs) != 0 { + if err := reject("$defs"); err != nil { + return stack, err + } + } + if schema.SchemaDialect != "" { + if err := reject("$schema"); err != nil { + return stack, err + } + } + if schema.Comment != "" { + if err := reject("$comment"); err != nil { + return stack, err + } + } + if schema.SchemaID != "" { + if err := reject("$id"); err != nil { + return stack, err + } + } + if schema.Anchor != "" { + if err := reject("$anchor"); err != nil { + return stack, err + } + } + if schema.DynamicRef != "" { + if err := reject("$dynamicRef"); err != nil { + return stack, err + } + } + if schema.DynamicAnchor != "" { + if err := reject("$dynamicAnchor"); err != nil { + return stack, err + } + } + if schema.ContentMediaType != "" { + if err := reject("contentMediaType"); err != nil { + return stack, err + } + } + if schema.ContentEncoding != "" { + if err := reject("contentEncoding"); err != nil { + return stack, err + } + } + if schema.ContentSchema != nil { + if err := reject("contentSchema"); err != nil { + return stack, err + } + } } for _, item := range schema.OneOf { v := item.Value if v == nil { - return stack, foundUnresolvedRef(item.Ref) + return stack, newUnresolvedRef(item.Ref, item.Origin) } var err error @@ -942,7 +1563,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, for _, item := range schema.AnyOf { v := item.Value if v == nil { - return stack, foundUnresolvedRef(item.Ref) + return stack, newUnresolvedRef(item.Ref, item.Origin) } var err error @@ -954,7 +1575,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, for _, item := range schema.AllOf { v := item.Value if v == nil { - return stack, foundUnresolvedRef(item.Ref) + return stack, newUnresolvedRef(item.Ref, item.Origin) } var err error @@ -966,7 +1587,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, if ref := schema.Not; ref != nil { v := ref.Value if v == nil { - return stack, foundUnresolvedRef(ref.Ref) + return stack, newUnresolvedRef(ref.Ref, ref.Origin) } var err error @@ -975,6 +1596,37 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, } } + if ref := schema.If; ref != nil { + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if ref := schema.Then; ref != nil { + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if ref := schema.Else; ref != nil { + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + for _, schemaType := range schema.Type.Slice() { switch schemaType { case TypeBoolean: @@ -1025,19 +1677,26 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, } } case TypeArray: - if schema.Items == nil { - return stack, errors.New("when schema type is 'array', schema 'items' must be non-null") + if schema.Items == nil && !validationOpts.jsonSchema2020ValidationEnabled && len(schema.PrefixItems) == 0 { + return stack, newSchemaItemsRequired(schema.Origin) } case TypeObject: + case TypeNull: + if !validationOpts.jsonSchema2020ValidationEnabled { + return stack, newSchemaTypeError(schemaType, schema.Origin) + } default: - return stack, fmt.Errorf("unsupported 'type' value %q", schemaType) + return stack, newSchemaTypeError(schemaType, schema.Origin) } } if ref := schema.Items; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } v := ref.Value if v == nil { - return stack, foundUnresolvedRef(ref.Ref) + return stack, newUnresolvedRef(ref.Ref, ref.Origin) } var err error @@ -1046,16 +1705,14 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, } } - properties := make([]string, 0, len(schema.Properties)) - for name := range schema.Properties { - properties = append(properties, name) - } - slices.Sort(properties) - for _, name := range properties { + for _, name := range componentNames(schema.Properties) { ref := schema.Properties[name] + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } v := ref.Value if v == nil { - return stack, foundUnresolvedRef(ref.Ref) + return stack, newUnresolvedRef(ref.Ref, ref.Origin) } var err error @@ -1065,12 +1722,152 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, } if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil { - return stack, errors.New("additionalProperties are set to both boolean and schema") + return stack, newSchemaAdditionalPropertiesBothForms(schema.Origin) } if ref := schema.AdditionalProperties.Schema; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + // OpenAPI 3.1 / JSON Schema 2020-12 sub-schemas + for _, ref := range schema.PrefixItems { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if ref := schema.Contains; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + for _, name := range componentNames(schema.PatternProperties) { + ref := schema.PatternProperties[name] + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + for _, name := range componentNames(schema.DependentSchemas) { + ref := schema.DependentSchemas[name] + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + for _, name := range componentNames(schema.Defs) { + ref := schema.Defs[name] + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if ref := schema.PropertyNames; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } v := ref.Value if v == nil { - return stack, foundUnresolvedRef(ref.Ref) + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if schema.UnevaluatedItems.Has != nil && schema.UnevaluatedItems.Schema != nil { + return stack, newSchemaUnevaluatedItemsBothForms(schema.Origin) + } + if ref := schema.UnevaluatedItems.Schema; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if schema.UnevaluatedProperties.Has != nil && schema.UnevaluatedProperties.Schema != nil { + return stack, newSchemaUnevaluatedPropertiesBothForms(schema.Origin) + } + if ref := schema.UnevaluatedProperties.Schema; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + if ref := schema.ContentSchema; ref != nil { + if err := ref.validateExtras(ctx); err != nil { + return stack, err + } + v := ref.Value + if v == nil { + return stack, newUnresolvedRef(ref.Ref, ref.Origin) } var err error @@ -1081,23 +1878,23 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, if v := schema.ExternalDocs; v != nil { if err := v.Validate(ctx); err != nil { - return stack, fmt.Errorf("invalid external docs: %w", err) + return stack, &SectionValidationError{Section: "external docs", Cause: err} } } if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled { - if err := schema.VisitJSON(v); err != nil { - return stack, fmt.Errorf("invalid default: %w", err) + if err := validateExampleValue(ctx, v, schema); err != nil { + return stack, newSchemaValueError("default", err, schema.Origin) } } if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled { if err := validateExampleValue(ctx, x, schema); err != nil { - return stack, fmt.Errorf("invalid example: %w", err) + return stack, newSchemaValueError("example", err, schema.Origin) } } - return stack, validateExtensions(ctx, schema.Extensions) + return stack, validateExtensions(ctx, schema.Extensions, schema.Origin) } func (schema *Schema) IsMatching(value any) bool { @@ -1130,8 +1927,14 @@ func (schema *Schema) IsMatchingJSONObject(value map[string]any) bool { return schema.visitJSON(settings, value) == nil } +// VisitJSON applies a Schema to the given data, considering opts. +// To validate data against an OpenAPIv3.1+ schema, be sure to pass the EnableJSONSchema2020() option. func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error { settings := newSchemaValidationSettings(opts...) + + if settings.useJSONSchema2020 { + return schema.useJSONSchema2020(settings, value) + } return schema.visitJSON(settings, value) } @@ -1171,6 +1974,9 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) ( if err = schema.visitEnumOperation(settings, value); err != nil { return } + if err = schema.visitConstOperation(settings, value); err != nil { + return + } switch value := value.(type) { case nil: @@ -1265,7 +2071,40 @@ func (schema *Schema) visitEnumOperation(settings *schemaValidationSettings, val Value: value, Schema: schema, SchemaField: "enum", - Reason: fmt.Sprintf("value is not one of the allowed values %s", string(allowedValues)), + Reason: "value is not one of the allowed values " + string(allowedValues), + customizeMessageError: settings.customizeMessageError, + } + } + return +} + +func (schema *Schema) visitConstOperation(settings *schemaValidationSettings, value any) (err error) { + if schema.Const == nil { + return + } + var match bool + switch c := value.(type) { + case json.Number: + var f float64 + if f, err = strconv.ParseFloat(c.String(), 64); err != nil { + return err + } + match = reflect.DeepEqual(schema.Const, f) + case int64: + match = reflect.DeepEqual(schema.Const, float64(c)) + default: + match = reflect.DeepEqual(schema.Const, value) + } + if !match { + if settings.failfast { + return errSchema + } + constVal, _ := json.Marshal(schema.Const) + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "const", + Reason: "value must be " + string(constVal), customizeMessageError: settings.customizeMessageError, } } @@ -1276,7 +2115,7 @@ func (schema *Schema) visitNotOperation(settings *schemaValidationSettings, valu if ref := schema.Not; ref != nil { v := ref.Value if v == nil { - return foundUnresolvedRef(ref.Ref) + return newUnresolvedRef(ref.Ref, ref.Origin) } if err := v.visitJSON(settings, value); err == nil { if settings.failfast { @@ -1295,41 +2134,54 @@ func (schema *Schema) visitNotOperation(settings *schemaValidationSettings, valu // If the XOF operations pass successfully, abort further run of validation, as they will already be satisfied (unless the schema // itself is badly specified +// resolveDiscriminatorRef resolves the discriminator reference for oneOf/anyOf validation. +// Returns the discriminator ref string and any error encountered during resolution. +func (schema *Schema) resolveDiscriminatorRef(value any) (string, error) { + if schema.Discriminator == nil { + return "", nil + } + pn := schema.Discriminator.PropertyName + valuemap, okcheck := value.(map[string]any) + if !okcheck { + return "", nil + } + discriminatorVal, okcheck := valuemap[pn] + if !okcheck { + return "", &SchemaError{ + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("input does not contain the discriminator property %q", pn), + } + } + + discriminatorValString, okcheck := discriminatorVal.(string) + if !okcheck { + return "", &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn), + } + } + + if discriminatorRef, okcheck := schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck { + return "", &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("discriminator property %q has invalid value", pn), + } + } else { + return discriminatorRef.Ref, nil + } +} + func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, value any) (err error, run bool) { var visitedOneOf, visitedAnyOf, visitedAllOf bool if v := schema.OneOf; len(v) > 0 { - var discriminatorRef MappingRef - if schema.Discriminator != nil { - pn := schema.Discriminator.PropertyName - if valuemap, okcheck := value.(map[string]any); okcheck { - discriminatorVal, okcheck := valuemap[pn] - if !okcheck { - return &SchemaError{ - Schema: schema, - SchemaField: "discriminator", - Reason: fmt.Sprintf("input does not contain the discriminator property %q", pn), - }, false - } - - discriminatorValString, okcheck := discriminatorVal.(string) - if !okcheck { - return &SchemaError{ - Value: discriminatorVal, - Schema: schema, - SchemaField: "discriminator", - Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn), - }, false - } - - if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck { - return &SchemaError{ - Value: discriminatorVal, - Schema: schema, - SchemaField: "discriminator", - Reason: fmt.Sprintf("discriminator property %q has invalid value", pn), - }, false - } - } + discriminatorRef, err := schema.resolveDiscriminatorRef(value) + if err != nil { + return err, false } var ( @@ -1341,10 +2193,10 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val for idx, item := range v { v := item.Value if v == nil { - return foundUnresolvedRef(item.Ref), false + return newUnresolvedRef(item.Ref, item.Origin), false } - if discriminatorRef.Ref != "" && discriminatorRef.Ref != item.Ref { + if discriminatorRef != "" && discriminatorRef != item.Ref { continue } @@ -1391,6 +2243,11 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val } if v := schema.AnyOf; len(v) > 0 { + discriminatorRef, err := schema.resolveDiscriminatorRef(value) + if err != nil { + return err, false + } + var ( ok = false matchedAnyOfIdx = 0 @@ -1399,8 +2256,13 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val for idx, item := range v { v := item.Value if v == nil { - return foundUnresolvedRef(item.Ref), false + return newUnresolvedRef(item.Ref, item.Origin), false + } + + if discriminatorRef != "" && discriminatorRef != item.Ref { + continue } + // make a deep copy to protect origin value from being injected default value that defined in mismatched anyOf schema if settings.asreq || settings.asrep { tempValue = deepcopy.Copy(value) @@ -1432,7 +2294,7 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val for _, item := range schema.AllOf { v := item.Value if v == nil { - return foundUnresolvedRef(item.Ref), false + return newUnresolvedRef(item.Ref, item.Origin), false } if err := v.visitJSON(settings, value); err != nil { if settings.failfast { @@ -1582,39 +2444,73 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value } // "exclusiveMinimum" - if v := schema.ExclusiveMin; v && !(*schema.Min < value) { - if settings.failfast { - return errSchema - } - err := &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "exclusiveMinimum", - Reason: fmt.Sprintf("number must be more than %g", *schema.Min), - customizeMessageError: settings.customizeMessageError, + // OpenAPI 3.0: boolean modifier for minimum + // OpenAPI 3.1: number representing the actual exclusive bound + if eb := schema.ExclusiveMin; eb.IsSet() { + var exclusiveMinBound float64 + var valid bool + if eb.Value != nil { + // OpenAPI 3.1 style: exclusiveMinimum is the bound itself + exclusiveMinBound = *eb.Value + valid = value > exclusiveMinBound + } else if eb.Bool != nil && *eb.Bool && schema.Min != nil { + // OpenAPI 3.0 style: exclusiveMinimum modifies minimum + exclusiveMinBound = *schema.Min + valid = value > exclusiveMinBound + } else { + valid = true } - if !settings.multiError { - return err + if !valid { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMinimum", + Reason: fmt.Sprintf("number must be more than %g", exclusiveMinBound), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) } - me = append(me, err) } // "exclusiveMaximum" - if v := schema.ExclusiveMax; v && !(*schema.Max > value) { - if settings.failfast { - return errSchema - } - err := &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "exclusiveMaximum", - Reason: fmt.Sprintf("number must be less than %g", *schema.Max), - customizeMessageError: settings.customizeMessageError, + // OpenAPI 3.0: boolean modifier for maximum + // OpenAPI 3.1: number representing the actual exclusive bound + if eb := schema.ExclusiveMax; eb.IsSet() { + var exclusiveMaxBound float64 + var valid bool + if eb.Value != nil { + // OpenAPI 3.1 style: exclusiveMaximum is the bound itself + exclusiveMaxBound = *eb.Value + valid = value < exclusiveMaxBound + } else if eb.Bool != nil && *eb.Bool && schema.Max != nil { + // OpenAPI 3.0 style: exclusiveMaximum modifies maximum + exclusiveMaxBound = *schema.Max + valid = value < exclusiveMaxBound + } else { + valid = true } - if !settings.multiError { - return err + if !valid { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMaximum", + Reason: fmt.Sprintf("number must be less than %g", exclusiveMaxBound), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) } - me = append(me, err) } // "minimum" @@ -1893,7 +2789,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ if itemSchemaRef := schema.Items; itemSchemaRef != nil { itemSchema := itemSchemaRef.Value if itemSchema == nil { - return foundUnresolvedRef(itemSchemaRef.Ref) + return newUnresolvedRef(itemSchemaRef.Ref, itemSchemaRef.Origin) } for i, item := range value { if err := itemSchema.visitJSON(settings, item); err != nil { @@ -1930,12 +2826,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value var me MultiError if settings.asreq || settings.asrep { - properties := make([]string, 0, len(schema.Properties)) - for propName := range schema.Properties { - properties = append(properties, propName) - } - slices.Sort(properties) - for _, propName := range properties { + for _, propName := range componentNames(schema.Properties) { propSchema := schema.Properties[propName] reqRO := settings.asreq && propSchema.Value.ReadOnly && !settings.readOnlyValidationDisabled repWO := settings.asrep && propSchema.Value.WriteOnly && !settings.writeOnlyValidationDisabled @@ -2002,19 +2893,14 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value if ref := schema.AdditionalProperties.Schema; ref != nil { additionalProperties = ref.Value } - keys := make([]string, 0, len(value)) - for k := range value { - keys = append(keys, k) - } - slices.Sort(keys) - for _, k := range keys { + for _, k := range componentNames(value) { v := value[k] if properties != nil { propertyRef := properties[k] if propertyRef != nil { p := propertyRef.Value if p == nil { - return foundUnresolvedRef(propertyRef.Ref) + return newUnresolvedRef(propertyRef.Ref, propertyRef.Origin) } if err := p.visitJSON(settings, v); err != nil { if settings.failfast { diff --git a/openapi3/schema_allof_test.go b/openapi3/schema_allof_test.go index b241cb756..d03926483 100644 --- a/openapi3/schema_allof_test.go +++ b/openapi3/schema_allof_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "encoding/json" "errors" "testing" @@ -56,7 +55,7 @@ func TestAllOfErrorPreserved(t *testing.T) { s := NewSchema() err := s.UnmarshalJSON([]byte(schema)) require.NoError(t, err) - err = s.Validate(context.Background()) + err = s.Validate(t.Context()) require.NoError(t, err) obj := make(map[string]any) diff --git a/openapi3/schema_const_test.go b/openapi3/schema_const_test.go new file mode 100644 index 000000000..1867e7475 --- /dev/null +++ b/openapi3/schema_const_test.go @@ -0,0 +1,94 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestSchemaConst_BuiltInValidator(t *testing.T) { + t.Run("string const", func(t *testing.T) { + schema := &openapi3.Schema{ + Const: "production", + } + + err := schema.VisitJSON("production") + require.NoError(t, err) + + err = schema.VisitJSON("development") + require.Error(t, err) + require.ErrorContains(t, err, "const") + }) + + t.Run("number const", func(t *testing.T) { + schema := &openapi3.Schema{ + Const: float64(42), + } + + err := schema.VisitJSON(float64(42)) + require.NoError(t, err) + + err = schema.VisitJSON(float64(43)) + require.Error(t, err) + }) + + t.Run("boolean const", func(t *testing.T) { + schema := &openapi3.Schema{ + Const: true, + } + + err := schema.VisitJSON(true) + require.NoError(t, err) + + err = schema.VisitJSON(false) + require.Error(t, err) + }) + + t.Run("null const", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"null"}, + Const: nil, + } + + // nil const means "not set", so this should pass as empty schema + err := schema.VisitJSON(nil) + require.NoError(t, err) + }) + + t.Run("object const", func(t *testing.T) { + schema := &openapi3.Schema{ + Const: map[string]any{"key": "value"}, + } + + err := schema.VisitJSON(map[string]any{"key": "value"}) + require.NoError(t, err) + + err = schema.VisitJSON(map[string]any{"key": "other"}) + require.Error(t, err) + }) + + t.Run("const with type constraint", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Const: "fixed", + } + + err := schema.VisitJSON("fixed") + require.NoError(t, err) + + err = schema.VisitJSON("other") + require.Error(t, err) + }) + + t.Run("const with multiError", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Const: "fixed", + } + + err := schema.VisitJSON("other", openapi3.MultiErrors()) + require.Error(t, err) + }) +} diff --git a/openapi3/schema_formats.go b/openapi3/schema_formats.go index da954dc58..5053510a3 100644 --- a/openapi3/schema_formats.go +++ b/openapi3/schema_formats.go @@ -7,18 +7,19 @@ import ( "regexp" ) -type ( - // FormatValidator is an interface for custom format validators. - FormatValidator[T any] interface { - Validate(value T) error - } - // StringFormatValidator is a type alias for FormatValidator[string] - StringFormatValidator = FormatValidator[string] - // NumberFormatValidator is a type alias for FormatValidator[float64] - NumberFormatValidator = FormatValidator[float64] - // IntegerFormatValidator is a type alias for FormatValidator[int64] - IntegerFormatValidator = FormatValidator[int64] -) +// FormatValidator is an interface for custom format validators. +type FormatValidator[T any] interface { + Validate(value T) error +} + +// StringFormatValidator is a type alias for FormatValidator[string] +type StringFormatValidator = FormatValidator[string] + +// NumberFormatValidator is a type alias for FormatValidator[float64] +type NumberFormatValidator = FormatValidator[float64] + +// IntegerFormatValidator is a type alias for FormatValidator[int64] +type IntegerFormatValidator = FormatValidator[int64] var ( // SchemaStringFormats is a map of custom string format validators. diff --git a/openapi3/schema_formats_test.go b/openapi3/schema_formats_test.go index 7ae3c5035..682ac6f27 100644 --- a/openapi3/schema_formats_test.go +++ b/openapi3/schema_formats_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "errors" "fmt" "testing" @@ -19,7 +18,7 @@ func TestIssue430(t *testing.T) { delete(SchemaStringFormats, "ipv4") delete(SchemaStringFormats, "ipv6") - err := schema.Validate(context.Background()) + err := schema.Validate(t.Context()) require.NoError(t, err) data := map[string]bool{ diff --git a/openapi3/schema_if_then_else_test.go b/openapi3/schema_if_then_else_test.go new file mode 100644 index 000000000..d2a6db6a1 --- /dev/null +++ b/openapi3/schema_if_then_else_test.go @@ -0,0 +1,211 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestSchemaIfThenElse_BuiltInValidator(t *testing.T) { + t.Run("schema with if/then/else is not empty", func(t *testing.T) { + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Then: &openapi3.SchemaRef{Value: &openapi3.Schema{MinLength: 3}}, + Else: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + } + require.False(t, schema.IsEmpty()) + }) + + t.Run("schema with dependentRequired is not empty", func(t *testing.T) { + schema := &openapi3.Schema{ + DependentRequired: map[string][]string{ + "creditCard": {"billingAddress"}, + }, + } + require.False(t, schema.IsEmpty()) + }) +} + +func TestSchemaIfThenElse_JSONSchema2020(t *testing.T) { + t.Run("if/then/else conditional validation", func(t *testing.T) { + // If type is string, then minLength=3; else must be number + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Then: &openapi3.SchemaRef{Value: &openapi3.Schema{MinLength: 3}}, + Else: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + } + + // String with length >= 3 → passes if+then + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Number → fails if, passes else + err = schema.VisitJSON(float64(42), openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Short string → passes if, fails then + err = schema.VisitJSON("ab", openapi3.EnableJSONSchema2020()) + require.Error(t, err) + + // Boolean → fails if, fails else + err = schema.VisitJSON(true, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("if/then without else", func(t *testing.T) { + // If type is string, then minLength=5 + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Then: &openapi3.SchemaRef{Value: &openapi3.Schema{MinLength: 5}}, + } + + // String with length >= 5 → passes + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Short string → fails then + err = schema.VisitJSON("hi", openapi3.EnableJSONSchema2020()) + require.Error(t, err) + + // Number → fails if, no else so passes + err = schema.VisitJSON(float64(42), openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + }) + + t.Run("dependentRequired validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + "creditCard": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + "billingAddress": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + }, + DependentRequired: map[string][]string{ + "creditCard": {"billingAddress"}, + }, + } + + // Has creditCard and billingAddress → passes + err := schema.VisitJSON(map[string]any{ + "name": "John", + "creditCard": "1234", + "billingAddress": "123 Main St", + }, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // No creditCard → passes (dependency not triggered) + err = schema.VisitJSON(map[string]any{ + "name": "John", + }, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + // Has creditCard but no billingAddress → fails + err = schema.VisitJSON(map[string]any{ + "name": "John", + "creditCard": "1234", + }, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) +} + +func TestSchemaIfThenElse_MarshalRoundTrip(t *testing.T) { + t.Run("if/then/else round-trip", func(t *testing.T) { + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Then: &openapi3.SchemaRef{Value: &openapi3.Schema{MinLength: 3}}, + Else: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + } + + data, err := schema.MarshalJSON() + require.NoError(t, err) + + var roundTripped openapi3.Schema + err = roundTripped.UnmarshalJSON(data) + require.NoError(t, err) + + require.NotNil(t, roundTripped.If) + require.NotNil(t, roundTripped.Then) + require.NotNil(t, roundTripped.Else) + require.True(t, roundTripped.If.Value.Type.Is("string")) + require.Equal(t, uint64(3), roundTripped.Then.Value.MinLength) + require.True(t, roundTripped.Else.Value.Type.Is("number")) + }) + + t.Run("dependentRequired round-trip", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + DependentRequired: map[string][]string{ + "creditCard": {"billingAddress", "cvv"}, + }, + } + + data, err := schema.MarshalJSON() + require.NoError(t, err) + + var roundTripped openapi3.Schema + err = roundTripped.UnmarshalJSON(data) + require.NoError(t, err) + + require.Equal(t, map[string][]string{ + "creditCard": {"billingAddress", "cvv"}, + }, roundTripped.DependentRequired) + }) + + t.Run("no extensions leak", func(t *testing.T) { + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + DependentRequired: map[string][]string{"a": {"b"}}, + } + + data, err := schema.MarshalJSON() + require.NoError(t, err) + + var roundTripped openapi3.Schema + err = roundTripped.UnmarshalJSON(data) + require.NoError(t, err) + + // if/then/else/dependentRequired should not leak into extensions + require.Nil(t, roundTripped.Extensions) + }) +} + +func TestSchemaIfThenElse_Validate(t *testing.T) { + t.Run("unresolved if ref fails validation", func(t *testing.T) { + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Ref: "#/components/schemas/Missing"}, + } + err := schema.Validate(t.Context(), openapi3.IsOpenAPI31OrLater()) + require.Error(t, err) + require.ErrorContains(t, err, "unresolved ref") + }) + + t.Run("unresolved then ref fails validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Then: &openapi3.SchemaRef{Ref: "#/components/schemas/Missing"}, + } + err := schema.Validate(t.Context(), openapi3.IsOpenAPI31OrLater()) + require.Error(t, err) + require.ErrorContains(t, err, "unresolved ref") + }) + + t.Run("unresolved else ref fails validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Else: &openapi3.SchemaRef{Ref: "#/components/schemas/Missing"}, + } + err := schema.Validate(t.Context(), openapi3.IsOpenAPI31OrLater()) + require.Error(t, err) + require.ErrorContains(t, err, "unresolved ref") + }) + + t.Run("valid if/then/else passes validation", func(t *testing.T) { + schema := &openapi3.Schema{ + If: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Then: &openapi3.SchemaRef{Value: &openapi3.Schema{MinLength: 1}}, + Else: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + } + err := schema.Validate(t.Context(), openapi3.IsOpenAPI31OrLater()) + require.NoError(t, err) + }) +} diff --git a/openapi3/schema_issue289_test.go b/openapi3/schema_issue289_test.go index 033371900..2d47f7d2c 100644 --- a/openapi3/schema_issue289_test.go +++ b/openapi3/schema_issue289_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue289(t *testing.T) { @@ -34,7 +36,7 @@ info: paths: {} `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -45,5 +47,5 @@ paths: {} "name": "kin-openapi", "address": "127.0.0.1", }) - require.ErrorIs(t, err, ErrOneOfConflict) + require.ErrorIs(t, err, openapi3.ErrOneOfConflict) } diff --git a/openapi3/schema_issue492_test.go b/openapi3/schema_issue492_test.go index 151567226..d14eac40c 100644 --- a/openapi3/schema_issue492_test.go +++ b/openapi3/schema_issue492_test.go @@ -1,9 +1,11 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) func TestIssue492(t *testing.T) { @@ -27,7 +29,7 @@ info: title: title `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -46,5 +48,5 @@ info: "name": "kin-openapi", "time": "2001-02-03T04:05:06:789Z", }) - require.EqualError(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "`+FormatOfStringDateTime+`"`) + require.EqualError(t, err, `Error at "/time": string doesn't match the format "date-time": string doesn't match pattern "`+openapi3.FormatOfStringDateTime+`"`) } diff --git a/openapi3/schema_issue940_test.go b/openapi3/schema_issue940_test.go index 792199489..965ea1037 100644 --- a/openapi3/schema_issue940_test.go +++ b/openapi3/schema_issue940_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "encoding/json" "errors" "testing" @@ -43,7 +42,7 @@ func TestOneOfErrorPreserved(t *testing.T) { s := NewSchema() err := s.UnmarshalJSON([]byte(schema)) require.NoError(t, err) - err = s.Validate(context.Background()) + err = s.Validate(t.Context()) require.NoError(t, err) obj := make(map[string]any) diff --git a/openapi3/schema_jsonschema_validator.go b/openapi3/schema_jsonschema_validator.go new file mode 100644 index 000000000..d610e535d --- /dev/null +++ b/openapi3/schema_jsonschema_validator.go @@ -0,0 +1,212 @@ +package openapi3 + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/santhosh-tekuri/jsonschema/v6" +) + +// jsonSchemaValidator wraps the santhosh-tekuri/jsonschema validator +type jsonSchemaValidator struct { + compiler *jsonschema.Compiler + schema *jsonschema.Schema +} + +// newJSONSchemaValidator creates a new validator using JSON Schema 2020-12 +func newJSONSchemaValidator(schema *Schema) (*jsonSchemaValidator, error) { + // Convert OpenAPI Schema to JSON Schema format + schemaBytes, err := json.Marshal(schema) + if err != nil { + return nil, fmt.Errorf("failed to marshal schema: %w", err) + } + + var schemaMap map[string]any + if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal schema: %w", err) + } + + // OpenAPI 3.1 specific transformations + transformOpenAPIToJSONSchema(schemaMap) + + // Create compiler + compiler := jsonschema.NewCompiler() + compiler.DefaultDraft(jsonschema.Draft2020) + + // Add the schema + schemaURL := "https://example.com/schema.json" + if err := compiler.AddResource(schemaURL, schemaMap); err != nil { + return nil, fmt.Errorf("failed to add schema resource: %w", err) + } + + // Compile the schema + compiledSchema, err := compiler.Compile(schemaURL) + if err != nil { + return nil, fmt.Errorf("failed to compile schema: %w", err) + } + + return &jsonSchemaValidator{ + compiler: compiler, + schema: compiledSchema, + }, nil +} + +// transformOpenAPIToJSONSchema converts OpenAPI 3.0/3.1 specific keywords to JSON Schema format +func transformOpenAPIToJSONSchema(schema map[string]any) { + // Handle nullable - in OpenAPI 3.0, nullable is a boolean flag + // In OpenAPI 3.1 / JSON Schema 2020-12, we use type arrays + if nullable, ok := schema["nullable"].(bool); ok && nullable { + if typeVal, ok := schema["type"].(string); ok { + // Convert to type array with null + schema["type"] = []string{typeVal, "null"} + } else if _, hasType := schema["type"]; !hasType { + // nullable: true without type - add "null" to allow null values + schema["type"] = []string{"null"} + } + delete(schema, "nullable") + } + + // Handle exclusiveMinimum/exclusiveMaximum + // In OpenAPI 3.0, these are booleans alongside minimum/maximum + // In JSON Schema 2020-12, they are numeric values + if exclusiveMin, ok := schema["exclusiveMinimum"].(bool); ok { + if exclusiveMin { + if schemaMin, ok := schema["minimum"].(float64); ok { + schema["exclusiveMinimum"] = schemaMin + delete(schema, "minimum") + } else { + delete(schema, "exclusiveMinimum") + } + } else { + // exclusiveMinimum: false means inclusive, which is the JSON Schema default + delete(schema, "exclusiveMinimum") + } + } + if exclusiveMax, ok := schema["exclusiveMaximum"].(bool); ok { + if exclusiveMax { + if schemaMax, ok := schema["maximum"].(float64); ok { + schema["exclusiveMaximum"] = schemaMax + delete(schema, "maximum") + } else { + delete(schema, "exclusiveMaximum") + } + } else { + // exclusiveMaximum: false means inclusive, which is the JSON Schema default + delete(schema, "exclusiveMaximum") + } + } + + // Remove OpenAPI-specific keywords that aren't in JSON Schema + delete(schema, "discriminator") + delete(schema, "xml") + delete(schema, "externalDocs") + delete(schema, "example") // Use "examples" in 2020-12 + + // Recursively transform nested schemas (single schema fields) + for _, key := range []string{ + "additionalProperties", "items", "not", + // OpenAPI 3.1 / JSON Schema 2020-12 fields + "contains", "propertyNames", "unevaluatedItems", "unevaluatedProperties", + "if", "then", "else", "contentSchema", + } { + if val, ok := schema[key]; ok { + if nestedSchema, ok := val.(map[string]any); ok { + transformOpenAPIToJSONSchema(nestedSchema) + } + } + } + + // Transform schema arrays (oneOf, anyOf, allOf, prefixItems) + for _, key := range []string{"oneOf", "anyOf", "allOf", "prefixItems"} { + if val, ok := schema[key].([]any); ok { + for _, item := range val { + if nestedSchema, ok := item.(map[string]any); ok { + transformOpenAPIToJSONSchema(nestedSchema) + } + } + } + } + + // Transform schema maps (properties, patternProperties, dependentSchemas, $defs) + for _, key := range []string{"properties", "patternProperties", "dependentSchemas", "$defs"} { + if props, ok := schema[key].(map[string]any); ok { + for _, propVal := range props { + if propSchema, ok := propVal.(map[string]any); ok { + transformOpenAPIToJSONSchema(propSchema) + } + } + } + } +} + +// validate validates a value against the compiled JSON Schema +func (v *jsonSchemaValidator) validate(value any) error { + if err := v.schema.Validate(value); err != nil { + // Convert jsonschema error to SchemaError + return convertJSONSchemaError(err) + } + return nil +} + +// convertJSONSchemaError converts a jsonschema validation error to OpenAPI SchemaError format +func convertJSONSchemaError(err error) error { + // TODO: Go 1.26 + // if err, ok := errors.AsType[*jsonschema.ValidationError](err); ok { + // return formatValidationError(err, "") + var validationErr *jsonschema.ValidationError + if errors.As(err, &validationErr) { + return formatValidationError(validationErr, "") + } + return err +} + +// formatValidationError recursively formats validation errors +func formatValidationError(verr *jsonschema.ValidationError, parentPath string) error { + // Build the path from InstanceLocation slice + path := "/" + strings.Join(verr.InstanceLocation, "/") + if parentPath != "" && path != "/" { + path = parentPath + path + } else if path == "/" { + path = parentPath + } + + // Build error message using the Error() method + var msg strings.Builder + if path != "" { + fmt.Fprintf(&msg, `error at "%s": `, path) + } + msg.WriteString(verr.Error()) + + // If there are sub-errors, format them too + if len(verr.Causes) > 0 { + var subErrors MultiError + for _, cause := range verr.Causes { + if subErr := formatValidationError(cause, path); subErr != nil { + subErrors = append(subErrors, subErr) + } + } + if len(subErrors) > 0 { + return &SchemaError{ + Reason: msg.String(), + Origin: fmt.Errorf("validation failed due to: %w", subErrors), + } + } + } + + return &SchemaError{ + Reason: msg.String(), + } +} + +// useJSONSchema2020 validates using the JSON Schema 2020-12 validator +func (schema *Schema) useJSONSchema2020(settings *schemaValidationSettings, value any) error { + validator, err := newJSONSchemaValidator(schema) + if err != nil { + // Fall back to built-in validator if compilation fails + return schema.visitJSON(settings, value) + } + + return validator.validate(value) +} diff --git a/openapi3/schema_jsonschema_validator_test.go b/openapi3/schema_jsonschema_validator_test.go new file mode 100644 index 000000000..d79a2fb4b --- /dev/null +++ b/openapi3/schema_jsonschema_validator_test.go @@ -0,0 +1,421 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestJSONSchema2020Validator_Basic(t *testing.T) { + t.Run("string validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(123, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("number validation", func(t *testing.T) { + min := 0.0 + max := 100.0 + schema := &openapi3.Schema{ + Type: &openapi3.Types{"number"}, + Min: &min, + Max: &max, + } + + err := schema.VisitJSON(50.0, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(150.0, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + + err = schema.VisitJSON(-10.0, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("object validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + "age": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}, + }, + Required: []string{"name"}, + } + + err := schema.VisitJSON(map[string]any{ + "name": "John", + "age": 30, + }) + require.NoError(t, err) + + err = schema.VisitJSON(map[string]any{ + "age": 30, + }) + require.Error(t, err) // missing required "name" + }) + + t.Run("array validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + Items: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + }}, + } + + err := schema.VisitJSON([]any{"a", "b", "c"}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON([]any{"a", 1, "c"}, openapi3.EnableJSONSchema2020()) + require.Error(t, err) // item 1 is not a string + }) +} + +func TestJSONSchema2020Validator_OpenAPI31Features(t *testing.T) { + t.Run("type array with null", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(123, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("nullable conversion", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(nil, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + }) + + t.Run("const validation", func(t *testing.T) { + schema := &openapi3.Schema{ + Const: "fixed-value", + } + + err := schema.VisitJSON("fixed-value", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON("other-value", openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("examples field", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Examples: []any{ + "example1", + "example2", + }, + } + + // Examples don't affect validation, just ensure schema is valid + err := schema.VisitJSON("any-value", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + }) +} + +func TestJSONSchema2020Validator_ExclusiveMinMax(t *testing.T) { + t.Run("exclusive minimum as boolean (OpenAPI 3.0 style)", func(t *testing.T) { + min := 0.0 + boolTrue := true + schema := &openapi3.Schema{ + Type: &openapi3.Types{"number"}, + Min: &min, + ExclusiveMin: openapi3.ExclusiveBound{Bool: &boolTrue}, + } + + err := schema.VisitJSON(0.1, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(0.0, openapi3.EnableJSONSchema2020()) + require.Error(t, err) // should be exclusive + }) + + t.Run("exclusive maximum as boolean (OpenAPI 3.0 style)", func(t *testing.T) { + max := 100.0 + boolTrue := true + schema := &openapi3.Schema{ + Type: &openapi3.Types{"number"}, + Max: &max, + ExclusiveMax: openapi3.ExclusiveBound{Bool: &boolTrue}, + } + + err := schema.VisitJSON(99.9, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(100.0, openapi3.EnableJSONSchema2020()) + require.Error(t, err) // should be exclusive + }) +} + +func TestJSONSchema2020Validator_ComplexSchemas(t *testing.T) { + t.Run("oneOf", func(t *testing.T) { + schema := &openapi3.Schema{ + OneOf: openapi3.SchemaRefs{ + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + }, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(42, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(true, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("anyOf", func(t *testing.T) { + schema := &openapi3.Schema{ + AnyOf: openapi3.SchemaRefs{ + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + }, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(42, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(true, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("allOf", func(t *testing.T) { + min := 0.0 + max := 100.0 + schema := &openapi3.Schema{ + AllOf: openapi3.SchemaRefs{ + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}, + &openapi3.SchemaRef{Value: &openapi3.Schema{Min: &min}}, + &openapi3.SchemaRef{Value: &openapi3.Schema{Max: &max}}, + }, + } + + err := schema.VisitJSON(50.0, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(150.0, openapi3.EnableJSONSchema2020()) + require.Error(t, err) // exceeds max + }) + + t.Run("not", func(t *testing.T) { + schema := &openapi3.Schema{ + Not: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + } + + err := schema.VisitJSON(42, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) +} + +func TestJSONSchema2020Validator_Fallback(t *testing.T) { + t.Run("fallback on compilation error", func(t *testing.T) { + // Create a schema that might cause compilation issues + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + } + + // Should not panic, even if there's an issue + err := schema.VisitJSON("test", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + }) +} + +func TestJSONSchema2020Validator_TransformRecursesInto31Fields(t *testing.T) { + // These tests verify that transformOpenAPIToJSONSchema recurses into + // OpenAPI 3.1 / JSON Schema 2020-12 fields. Each sub-test uses a nested + // schema with nullable:true (an OpenAPI 3.0-ism) that must be converted + // to a type array for the JSON Schema 2020-12 validator to handle null. + + t.Run("prefixItems with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + PrefixItems: openapi3.SchemaRefs{ + &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + }, + } + + err := schema.VisitJSON([]any{"hello"}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON([]any{nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should be accepted after nullable conversion in prefixItems") + }) + + t.Run("contains with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + Contains: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + } + + err := schema.VisitJSON([]any{nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should match contains after nullable conversion") + }) + + t.Run("patternProperties with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + PatternProperties: openapi3.Schemas{ + "^x-": &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + }, + } + + err := schema.VisitJSON(map[string]any{"x-val": nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should be accepted after nullable conversion in patternProperties") + }) + + t.Run("dependentSchemas with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + "tag": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Nullable: true}}, + }, + DependentSchemas: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{ + Properties: openapi3.Schemas{ + "tag": &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + }, + }}, + }, + } + + err := schema.VisitJSON(map[string]any{"name": "foo", "tag": nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should be accepted after nullable conversion in dependentSchemas") + }) + + t.Run("propertyNames with nullable not applicable but transform should not crash", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + PropertyNames: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + MinLength: 1, + }}, + } + + err := schema.VisitJSON(map[string]any{"abc": 1}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(map[string]any{"": 1}, openapi3.EnableJSONSchema2020()) + require.Error(t, err, "empty property name should fail minLength") + }) + + t.Run("unevaluatedItems with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + PrefixItems: openapi3.SchemaRefs{ + &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}, + }, + UnevaluatedItems: openapi3.BoolSchema{Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}}, + } + + err := schema.VisitJSON([]any{1, nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should be accepted after nullable conversion in unevaluatedItems") + }) + + t.Run("unevaluatedProperties with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + }, + UnevaluatedProperties: openapi3.BoolSchema{Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}}, + } + + err := schema.VisitJSON(map[string]any{"name": "foo", "extra": nil}, openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "null should be accepted after nullable conversion in unevaluatedProperties") + }) + + t.Run("contentSchema with nullable nested schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + ContentMediaType: "application/json", + ContentSchema: &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Nullable: true, + }}, + } + + // contentSchema transform should not crash and should handle nullable + err := schema.VisitJSON("null", openapi3.EnableJSONSchema2020()) + require.NoError(t, err, "contentSchema transform should handle nullable nested schema") + }) +} + +func TestBuiltInValidatorStillWorks(t *testing.T) { + t.Run("string validation with built-in", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + } + + err := schema.VisitJSON("hello", openapi3.EnableJSONSchema2020()) + require.NoError(t, err) + + err = schema.VisitJSON(123, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) + + t.Run("object validation with built-in", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + }, + Required: []string{"name"}, + } + + err := schema.VisitJSON(map[string]any{ + "name": "John", + }) + require.NoError(t, err) + + err = schema.VisitJSON(map[string]any{}, openapi3.EnableJSONSchema2020()) + require.Error(t, err) + }) +} diff --git a/openapi3/schema_oneOf_test.go b/openapi3/schema_oneOf_test.go index 336c2843c..7f530b219 100644 --- a/openapi3/schema_oneOf_test.go +++ b/openapi3/schema_oneOf_test.go @@ -1,12 +1,14 @@ -package openapi3 +package openapi3_test import ( "testing" "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" ) -func oneofSpec(t *testing.T) *T { +func oneofSpec(t *testing.T) *openapi3.T { t.Helper() spec := []byte(` @@ -59,7 +61,7 @@ components: dog: "#/components/schemas/Dog" `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -69,7 +71,7 @@ components: return doc } -func oneofNoDiscriminatorSpec(t *testing.T) *T { +func oneofNoDiscriminatorSpec(t *testing.T) *openapi3.T { t.Helper() spec := []byte(` @@ -107,7 +109,7 @@ components: - $ref: "#/components/schemas/Dog" `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -198,7 +200,7 @@ components: maxLength: 10 `[1:]) - loader := NewLoader() + loader := openapi3.NewLoader() doc, err := loader.LoadFromData(spec) require.NoError(t, err) @@ -212,11 +214,8 @@ components: }, }, }) - require.ErrorContains(t, err, `Error at "/first/second/third"`) - - var sErr *SchemaError - + var sErr *openapi3.SchemaError require.ErrorAs(t, err, &sErr) require.Equal(t, []string{"first", "second", "third"}, sErr.JSONPointer()) } diff --git a/openapi3/schema_pattern.go b/openapi3/schema_pattern.go index 2010d57cc..0be88955d 100644 --- a/openapi3/schema_pattern.go +++ b/openapi3/schema_pattern.go @@ -21,12 +21,13 @@ func (schema *Schema) compilePattern(c RegexCompilerFunc) (cp RegexMatcher, err cp, err = regexp.Compile(intoGoRegexp(pattern)) } if err != nil { - err = &SchemaError{ + schemaErr := &SchemaError{ Schema: schema, SchemaField: "pattern", Origin: err, Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), } + err = newSchemaPatternRegexError(pattern, schemaErr, schema.Origin) return } diff --git a/openapi3/schema_pattern_test.go b/openapi3/schema_pattern_test.go index 988adfa45..9417871ec 100644 --- a/openapi3/schema_pattern_test.go +++ b/openapi3/schema_pattern_test.go @@ -8,7 +8,7 @@ import ( ) func TestPattern(t *testing.T) { - _, err := regexp.Compile("^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\\(\\`\\.\\\"\\']+$") + _, err := regexp.Compile("^[a-zA-Z\\u0080-\\u024F\\s\\/\\-\\)\\(\\`\\.\\\"\\']+$") //nolint:staticcheck require.EqualError(t, err, "error parsing regexp: invalid escape sequence: `\\u`") _, err = regexp.Compile(`^[a-zA-Z\x{0080}-\x{024F}]+$`) diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go index ef35dc2fa..cc28eb128 100644 --- a/openapi3/schema_test.go +++ b/openapi3/schema_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "encoding/base64" "encoding/json" "fmt" @@ -10,7 +9,7 @@ import ( "strings" "testing" - "github.com/oasdiff/yaml3" + yaml "github.com/oasdiff/yaml3" "github.com/stretchr/testify/require" ) @@ -1130,21 +1129,21 @@ type schemaTypeExample struct { func TestTypes(t *testing.T) { for _, example := range typeExamples { - t.Run(example.Title, testType(t, example)) + t.Run(example.Title, testType(example)) } } -func testType(t *testing.T, example schemaTypeExample) func(*testing.T) { +func testType(example schemaTypeExample) func(*testing.T) { return func(t *testing.T) { baseSchema := example.Schema for _, typ := range example.AllValid { schema := baseSchema.WithFormat(typ) - err := schema.Validate(context.Background()) + err := schema.Validate(t.Context()) require.NoError(t, err) } for _, typ := range example.AllInvalid { schema := baseSchema.WithFormat(typ) - ctx := WithValidationOptions(context.Background(), EnableSchemaFormatValidation()) + ctx := WithValidationOptions(t.Context(), EnableSchemaFormatValidation()) err := schema.Validate(ctx) require.Error(t, err) } @@ -1199,11 +1198,11 @@ var typeExamples = []schemaTypeExample{ func TestSchemaErrors(t *testing.T) { for _, example := range schemaErrorExamples { - t.Run(example.Title, testSchemaError(t, example)) + t.Run(example.Title, testSchemaError(example)) } } -func testSchemaError(t *testing.T, example schemaErrorExample) func(*testing.T) { +func testSchemaError(example schemaErrorExample) func(*testing.T) { return func(t *testing.T) { msg := example.Error.Error() require.True(t, strings.Contains(msg, example.Want)) @@ -1251,11 +1250,11 @@ type schemaMultiErrorExample struct { func TestSchemasMultiError(t *testing.T) { for _, example := range schemaMultiErrorExamples { - t.Run(example.Title, testSchemaMultiError(t, example)) + t.Run(example.Title, testSchemaMultiError(example)) } } -func testSchemaMultiError(t *testing.T, example schemaMultiErrorExample) func(*testing.T) { +func testSchemaMultiError(example schemaMultiErrorExample) func(*testing.T) { return func(t *testing.T) { schema := example.Schema for validateFuncIndex, validateFunc := range validateSchemaFuncs { @@ -1434,7 +1433,7 @@ func TestValidationFailsOnInvalidPattern(t *testing.T) { Type: &Types{"string"}, } - err := schema.Validate(context.Background()) + err := schema.Validate(t.Context()) require.Error(t, err) } @@ -1452,7 +1451,7 @@ enum: err := yaml.Unmarshal(data, &schema) require.NoError(t, err) - err = schema.Validate(context.Background()) + err = schema.Validate(t.Context()) require.NoError(t, err) err = schema.VisitJSON(42) diff --git a/openapi3/schema_types_test.go b/openapi3/schema_types_test.go new file mode 100644 index 000000000..a7e3e35a6 --- /dev/null +++ b/openapi3/schema_types_test.go @@ -0,0 +1,243 @@ +package openapi3_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestTypes_HelperMethods(t *testing.T) { + t.Run("IncludesNull", func(t *testing.T) { + // Single type without null + types := &openapi3.Types{"string"} + require.False(t, types.IncludesNull()) + + // Type array with null + types = &openapi3.Types{"string", "null"} + require.True(t, types.IncludesNull()) + + // Multiple types without null + types = &openapi3.Types{"string", "number"} + require.False(t, types.IncludesNull()) + + // Nil types + var nilTypes *openapi3.Types + require.False(t, nilTypes.IncludesNull()) + }) + + t.Run("IsMultiple", func(t *testing.T) { + // Single type + types := &openapi3.Types{"string"} + require.False(t, types.IsMultiple()) + + // Multiple types + types = &openapi3.Types{"string", "null"} + require.True(t, types.IsMultiple()) + + types = &openapi3.Types{"string", "number", "null"} + require.True(t, types.IsMultiple()) + + // Empty types + types = &openapi3.Types{} + require.False(t, types.IsMultiple()) + + // Nil types + var nilTypes *openapi3.Types + require.False(t, nilTypes.IsMultiple()) + }) + + t.Run("IsSingle", func(t *testing.T) { + // Single type + types := &openapi3.Types{"string"} + require.True(t, types.IsSingle()) + + // Multiple types + types = &openapi3.Types{"string", "null"} + require.False(t, types.IsSingle()) + + // Empty types + types = &openapi3.Types{} + require.False(t, types.IsSingle()) + + // Nil types + var nilTypes *openapi3.Types + require.False(t, nilTypes.IsSingle()) + }) + + t.Run("IsEmpty", func(t *testing.T) { + // Single type + types := &openapi3.Types{"string"} + require.False(t, types.IsEmpty()) + + // Multiple types + types = &openapi3.Types{"string", "null"} + require.False(t, types.IsEmpty()) + + // Empty types + types = &openapi3.Types{} + require.True(t, types.IsEmpty()) + + // Nil types + var nilTypes *openapi3.Types + require.True(t, nilTypes.IsEmpty()) + }) +} + +func TestTypes_ArraySerialization(t *testing.T) { + t.Run("single type serializes as string", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + } + + data, err := json.Marshal(schema) + require.NoError(t, err) + + // Should serialize as "type": "string" (not array) + require.Contains(t, string(data), `"type":"string"`) + require.NotContains(t, string(data), `"type":["string"]`) + }) + + t.Run("multiple types serialize as array", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string", "null"}, + } + + data, err := json.Marshal(schema) + require.NoError(t, err) + + // Should serialize as "type": ["string", "null"] + require.Contains(t, string(data), `"type":["string","null"]`) + }) + + t.Run("deserialize string to single type", func(t *testing.T) { + jsonData := []byte(`{"type":"string"}`) + + var schema openapi3.Schema + err := json.Unmarshal(jsonData, &schema) + require.NoError(t, err) + + require.NotNil(t, schema.Type) + require.True(t, schema.Type.IsSingle()) + require.True(t, schema.Type.Is("string")) + }) + + t.Run("deserialize array to multiple types", func(t *testing.T) { + jsonData := []byte(`{"type":["string","null"]}`) + + var schema openapi3.Schema + err := json.Unmarshal(jsonData, &schema) + require.NoError(t, err) + + require.NotNil(t, schema.Type) + require.True(t, schema.Type.IsMultiple()) + require.True(t, schema.Type.Includes("string")) + require.True(t, schema.Type.IncludesNull()) + }) +} + +func TestTypes_OpenAPI31Features(t *testing.T) { + t.Run("type array with null", func(t *testing.T) { + types := &openapi3.Types{"string", "null"} + + require.True(t, types.Includes("string")) + require.True(t, types.IncludesNull()) + require.True(t, types.IsMultiple()) + require.False(t, types.IsSingle()) + require.False(t, types.IsEmpty()) + + // Test Permits + require.True(t, types.Permits("string")) + require.True(t, types.Permits("null")) + require.False(t, types.Permits("number")) + }) + + t.Run("type array without null", func(t *testing.T) { + types := &openapi3.Types{"string", "number"} + + require.True(t, types.Includes("string")) + require.True(t, types.Includes("number")) + require.False(t, types.IncludesNull()) + require.True(t, types.IsMultiple()) + }) + + t.Run("OpenAPI 3.0 style single type", func(t *testing.T) { + types := &openapi3.Types{"string"} + + require.True(t, types.Is("string")) + require.True(t, types.Includes("string")) + require.False(t, types.IncludesNull()) + require.False(t, types.IsMultiple()) + require.True(t, types.IsSingle()) + }) +} + +func TestTypes_EdgeCases(t *testing.T) { + t.Run("nil types permits everything", func(t *testing.T) { + var types *openapi3.Types + + require.True(t, types.Permits("string")) + require.True(t, types.Permits("number")) + require.True(t, types.Permits("null")) + require.True(t, types.IsEmpty()) + }) + + t.Run("empty slice of types", func(t *testing.T) { + types := &openapi3.Types{} + + require.False(t, types.Includes("string")) + require.False(t, types.Permits("string")) + require.True(t, types.IsEmpty()) + require.False(t, types.IsSingle()) + require.False(t, types.IsMultiple()) + }) + + t.Run("Slice method", func(t *testing.T) { + types := &openapi3.Types{"string", "null"} + slice := types.Slice() + + require.Equal(t, []string{"string", "null"}, slice) + + // Nil types + var nilTypes *openapi3.Types + require.Nil(t, nilTypes.Slice()) + }) +} + +func TestTypes_BackwardCompatibility(t *testing.T) { + t.Run("existing Is method still works", func(t *testing.T) { + // Single type + types := &openapi3.Types{"string"} + require.True(t, types.Is("string")) + require.False(t, types.Is("number")) + + // Multiple types - Is should return false + types = &openapi3.Types{"string", "null"} + require.False(t, types.Is("string")) + require.False(t, types.Is("null")) + }) + + t.Run("existing Includes method still works", func(t *testing.T) { + types := &openapi3.Types{"string"} + require.True(t, types.Includes("string")) + require.False(t, types.Includes("number")) + + types = &openapi3.Types{"string", "null"} + require.True(t, types.Includes("string")) + require.True(t, types.Includes("null")) + require.False(t, types.Includes("number")) + }) + + t.Run("existing Permits method still works", func(t *testing.T) { + // Nil types permits everything + var types *openapi3.Types + require.True(t, types.Permits("anything")) + + // Specific types + types = &openapi3.Types{"string"} + require.True(t, types.Permits("string")) + require.False(t, types.Permits("number")) + }) +} diff --git a/openapi3/schema_validate_31_test.go b/openapi3/schema_validate_31_test.go new file mode 100644 index 000000000..1d1983f20 --- /dev/null +++ b/openapi3/schema_validate_31_test.go @@ -0,0 +1,111 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestSchemaValidate31SubSchemas(t *testing.T) { + ctx := openapi3.WithValidationOptions(t.Context(), openapi3.IsOpenAPI31OrLater()) + + // Helper: a schema with an invalid nested schema (pattern with bad regex) + invalidSchema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Pattern: "[invalid", + } + + t.Run("prefixItems with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + PrefixItems: openapi3.SchemaRefs{{Value: invalidSchema}}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in prefixItems") + }) + + t.Run("contains with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + Contains: &openapi3.SchemaRef{Value: invalidSchema}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in contains") + }) + + t.Run("patternProperties with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + PatternProperties: openapi3.Schemas{ + "^x-": {Value: invalidSchema}, + }, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in patternProperties") + }) + + t.Run("dependentSchemas with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + DependentSchemas: openapi3.Schemas{ + "name": {Value: invalidSchema}, + }, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in dependentSchemas") + }) + + t.Run("propertyNames with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + PropertyNames: &openapi3.SchemaRef{Value: invalidSchema}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in propertyNames") + }) + + t.Run("unevaluatedItems with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + UnevaluatedItems: openapi3.BoolSchema{Schema: &openapi3.SchemaRef{Value: invalidSchema}}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in unevaluatedItems") + }) + + t.Run("unevaluatedProperties with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + UnevaluatedProperties: openapi3.BoolSchema{Schema: &openapi3.SchemaRef{Value: invalidSchema}}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in unevaluatedProperties") + }) + + t.Run("contentSchema with invalid sub-schema", func(t *testing.T) { + schema := &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + ContentMediaType: "application/json", + ContentSchema: &openapi3.SchemaRef{Value: invalidSchema}, + } + err := schema.Validate(ctx) + require.Error(t, err, "should detect invalid sub-schema in contentSchema") + }) + + t.Run("valid 3.1 sub-schemas pass validation", func(t *testing.T) { + validSubSchema := &openapi3.Schema{Type: &openapi3.Types{"string"}} + schema := &openapi3.Schema{ + Type: &openapi3.Types{"array"}, + Items: &openapi3.SchemaRef{Value: validSubSchema}, + PrefixItems: openapi3.SchemaRefs{ + {Value: validSubSchema}, + }, + Contains: &openapi3.SchemaRef{Value: validSubSchema}, + UnevaluatedItems: openapi3.BoolSchema{Schema: &openapi3.SchemaRef{Value: validSubSchema}}, + } + err := schema.Validate(ctx) + require.NoError(t, err, "valid sub-schemas should pass validation") + }) +} diff --git a/openapi3/schema_validation_settings.go b/openapi3/schema_validation_settings.go index 9d70a6191..205d95d8f 100644 --- a/openapi3/schema_validation_settings.go +++ b/openapi3/schema_validation_settings.go @@ -21,6 +21,7 @@ type schemaValidationSettings struct { patternValidationDisabled bool readOnlyValidationDisabled bool writeOnlyValidationDisabled bool + useJSONSchema2020 bool // Use JSON Schema 2020-12 validator for OpenAPI 3.1 regexCompiler RegexCompilerFunc @@ -151,6 +152,13 @@ func WithIntegerFormatValidator(name string, validator IntegerFormatValidator) S } } +// EnableJSONSchema2020 enables JSON Schema 2020-12 compliant validation. +// This enables support for OpenAPI 3.1 and JSON Schema 2020-12 features. +// When enabled, validation uses the jsonschema library instead of the built-in validator. +func EnableJSONSchema2020() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.useJSONSchema2020 = true } +} + func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings { settings := &schemaValidationSettings{} for _, opt := range opts { diff --git a/openapi3/security_requirements.go b/openapi3/security_requirements.go index d5310d87a..36b6401b1 100644 --- a/openapi3/security_requirements.go +++ b/openapi3/security_requirements.go @@ -45,7 +45,7 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri // Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec. func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error { - ctx = WithValidationOptions(ctx, opts...) + // ctx = WithValidationOptions(ctx, opts...) return nil } diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go index bc5bc2112..efe7f20bb 100644 --- a/openapi3/security_scheme.go +++ b/openapi3/security_scheme.go @@ -3,13 +3,15 @@ package openapi3 import ( "context" "encoding/json" - "errors" "fmt" + "maps" "net/url" + "slices" ) // SecurityScheme is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object +// and https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object type SecurityScheme struct { Extensions map[string]any `json:"-" yaml:"-"` Origin *Origin `json:"-" yaml:"-"` @@ -63,9 +65,7 @@ func (ss SecurityScheme) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of SecurityScheme. func (ss SecurityScheme) MarshalYAML() (any, error) { m := make(map[string]any, 8+len(ss.Extensions)) - for k, v := range ss.Extensions { - m[k] = v - } + maps.Copy(m, ss.Extensions) if x := ss.Type; x != "" { m["type"] = x } @@ -163,16 +163,20 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption hasBearerFormat = true case "basic", "negotiate", "digest": default: - return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme) + return newInvalidHTTPScheme(scheme, ss.Origin) } case "oauth2": hasFlow = true case "openIdConnect": if ss.OpenIdConnectUrl == "" { - return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", ss.Name) + return newOpenIDConnectURLRequired(ss.Name, ss.Origin) + } + case "mutualTLS": + if !getValidationOptions(ctx).isOpenAPI31OrLater { + return errValueOfFieldFor31Plus(ss.Type, "type") } default: - return fmt.Errorf("security scheme 'type' can't be %q", ss.Type) + return newInvalidSecuritySchemeType(ss.Type, ss.Origin) } // Validate "in" and "name" @@ -180,37 +184,37 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption switch ss.In { case "query", "header", "cookie": default: - return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", ss.In) + return newAPIKeyInInvalid(ss.In, ss.Origin) } if ss.Name == "" { - return errors.New("security scheme of type 'apiKey' should have 'name'") + return newAPIKeySecuritySchemeNameRequired(ss.Origin) } } else if len(ss.In) > 0 { - return fmt.Errorf("security scheme of type %q can't have 'in'", ss.Type) + return newSecuritySchemeInForbidden(ss.Type, ss.Origin) } else if len(ss.Name) > 0 { - return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type) + return newSecuritySchemeNameForbidden(ss.Type, ss.Origin) } // Validate "format" // "bearerFormat" is an arbitrary string so we only check if the scheme supports it if !hasBearerFormat && len(ss.BearerFormat) > 0 { - return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", ss.Type) + return newSecuritySchemeBearerFormatForbidden(ss.Type, ss.Origin) } // Validate "flow" if hasFlow { flow := ss.Flows if flow == nil { - return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type) + return newSecuritySchemeFlowsRequired(ss.Type, ss.Origin) } if err := flow.Validate(ctx); err != nil { - return fmt.Errorf("security scheme 'flow' is invalid: %w", err) + return &SecuritySchemeFlowValidationError{Cause: err} } } else if ss.Flows != nil { - return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type) + return newSecuritySchemeFlowsForbidden(ss.Type, ss.Origin) } - return validateExtensions(ctx, ss.Extensions) + return validateExtensions(ctx, ss.Extensions, ss.Origin) } // OAuthFlows is specified by OpenAPI/Swagger standard version 3. @@ -246,9 +250,7 @@ func (flows OAuthFlows) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of OAuthFlows. func (flows OAuthFlows) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(flows.Extensions)) - for k, v := range flows.Extensions { - m[k] = v - } + maps.Copy(m, flows.Extensions) if x := flows.Implicit; x != nil { m["implicit"] = x } @@ -289,29 +291,29 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) if v := flows.Implicit; v != nil { if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil { - return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err) + return &OAuthFlowValidationError{FlowKind: "implicit", Cause: err} } } if v := flows.Password; v != nil { if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil { - return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err) + return &OAuthFlowValidationError{FlowKind: "password", Cause: err} } } if v := flows.ClientCredentials; v != nil { if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil { - return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err) + return &OAuthFlowValidationError{FlowKind: "clientCredentials", Cause: err} } } if v := flows.AuthorizationCode; v != nil { if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil { - return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err) + return &OAuthFlowValidationError{FlowKind: "authorizationCode", Cause: err} } } - return validateExtensions(ctx, flows.Extensions) + return validateExtensions(ctx, flows.Extensions, flows.Origin) } // OAuthFlow is specified by OpenAPI/Swagger standard version 3. @@ -338,9 +340,7 @@ func (flow OAuthFlow) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of OAuthFlow. func (flow OAuthFlow) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(flow.Extensions)) - for k, v := range flow.Extensions { - m[k] = v - } + maps.Copy(m, flow.Extensions) if x := flow.AuthorizationURL; x != "" { m["authorizationUrl"] = x } @@ -380,35 +380,30 @@ func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) e if v := flow.RefreshURL; v != "" { if _, err := url.Parse(v); err != nil { - return fmt.Errorf("field 'refreshUrl' is invalid: %w", err) + return &OAuthFlowFieldValidationError{Field: "refreshUrl", Cause: err} } } if flow.Scopes == nil { - return errors.New("field 'scopes' is missing") + return newOAuthFlowScopesRequired(flow.Origin) } - return validateExtensions(ctx, flow.Extensions) + return validateExtensions(ctx, flow.Extensions, flow.Origin) } func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) typeIn := func(types ...oAuthFlowType) bool { - for _, ty := range types { - if ty == typ { - return true - } - } - return false + return slices.Contains(types, typ) } if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true { switch { case flow.AuthorizationURL == "" && in: - return errors.New("field 'authorizationUrl' is empty or missing") + return newOAuthFlowAuthorizationURLRequired(flow.Origin) case flow.AuthorizationURL != "" && !in: - return errors.New("field 'authorizationUrl' should not be set") + return newOAuthFlowAuthorizationURLForbidden(flow.Origin) case flow.AuthorizationURL != "": if _, err := url.Parse(flow.AuthorizationURL); err != nil { return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err) @@ -419,9 +414,9 @@ func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ... if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true { switch { case flow.TokenURL == "" && in: - return errors.New("field 'tokenUrl' is empty or missing") + return newOAuthFlowTokenURLRequired(flow.Origin) case flow.TokenURL != "" && !in: - return errors.New("field 'tokenUrl' should not be set") + return newOAuthFlowTokenURLForbidden(flow.Origin) case flow.TokenURL != "": if _, err := url.Parse(flow.TokenURL); err != nil { return fmt.Errorf("field 'tokenUrl' is invalid: %w", err) diff --git a/openapi3/security_scheme_test.go b/openapi3/security_scheme_test.go index 790414ca2..115564e73 100644 --- a/openapi3/security_scheme_test.go +++ b/openapi3/security_scheme_test.go @@ -1,7 +1,6 @@ package openapi3 import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -20,7 +19,7 @@ func TestSecuritySchemaExample(t *testing.T) { err := ss.UnmarshalJSON(example.raw) require.NoError(t, err) - err = ss.Validate(context.Background()) + err = ss.Validate(t.Context()) if example.valid { require.NoError(t, err) } else { diff --git a/openapi3/server.go b/openapi3/server.go index c3fb44147..181eace24 100644 --- a/openapi3/server.go +++ b/openapi3/server.go @@ -4,9 +4,8 @@ import ( "context" "encoding/json" "errors" - "fmt" + "maps" "net/url" - "slices" "strings" ) @@ -16,13 +15,14 @@ type Servers []*Server // Validate returns an error if Servers does not comply with the OpenAPI spec. func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) for _, v := range servers { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } - return nil + return me.result() } // BasePath returns the base path of the first server in the list, or /. @@ -66,8 +66,8 @@ func (server *Server) BasePath() (string, error) { } uri := server.URL - for name, svar := range server.Variables { - uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default) + for _, name := range componentNames(server.Variables) { + uri = strings.ReplaceAll(uri, "{"+name+"}", server.Variables[name].Default) } u, err := url.ParseRequestURI(uri) @@ -94,9 +94,7 @@ func (server Server) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Server. func (server Server) MarshalYAML() (any, error) { m := make(map[string]any, 3+len(server.Extensions)) - for k, v := range server.Extensions { - m[k] = v - } + maps.Copy(m, server.Extensions) m["url"] = server.URL if x := server.Description; x != "" { m["description"] = x @@ -121,6 +119,7 @@ func (server *Server) UnmarshalJSON(data []byte) error { if len(x.Extensions) == 0 { x.Extensions = nil } + delete(x.Variables, originKey) *server = Server(x) return nil } @@ -197,38 +196,47 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) { } // Validate returns an error if Server does not comply with the OpenAPI spec. -func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) { +func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if server.URL == "" { - return errors.New("value of url must be a non-empty string") + if err := me.emit(newServerURLRequired(server.Origin)); err != nil { + return err + } } opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}") if opening != closing { - return errors.New("server URL has mismatched { and }") + if err := me.emit(newServerURLMismatchedBraces(server.URL, server.Origin)); err != nil { + return err + } } if opening != len(server.Variables) { - return errors.New("server has undeclared variables") + if err := me.emit(newServerURLUndeclaredVariables(server.URL, server.Origin)); err != nil { + return err + } } - variables := make([]string, 0, len(server.Variables)) - for name := range server.Variables { - variables = append(variables, name) - } - slices.Sort(variables) - for _, name := range variables { + for _, name := range componentNames(server.Variables) { v := server.Variables[name] if !strings.Contains(server.URL, "{"+name+"}") { - return errors.New("server has undeclared variables") + if err := me.emit(newServerURLUndeclaredVariables(server.URL, server.Origin)); err != nil { + return err + } + // Variable name doesn't appear in the URL template; descending + // into its Validate would surface findings under a variable + // the URL never references, with no resolution path until the + // URL is fixed. + continue } - if err = v.Validate(ctx); err != nil { - return + if err := me.emit(v.Validate(ctx)); err != nil { + return err } } - return validateExtensions(ctx, server.Extensions) + return me.finalize(validateExtensions(ctx, server.Extensions, server.Origin)) } // ServerVariable is specified by OpenAPI/Swagger standard version 3. @@ -263,9 +271,7 @@ func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of ServerVariable. func (serverVariable ServerVariable) MarshalYAML() (any, error) { m := make(map[string]any, 4+len(serverVariable.Extensions)) - for k, v := range serverVariable.Extensions { - m[k] = v - } + maps.Copy(m, serverVariable.Extensions) if x := serverVariable.Enum; len(x) != 0 { m["enum"] = x } @@ -305,8 +311,8 @@ func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...Vali if err != nil { return err } - return fmt.Errorf("field default is required in %s", data) + return newServerVariableDefaultRequired(string(data), serverVariable.Origin) } - return validateExtensions(ctx, serverVariable.Extensions) + return validateExtensions(ctx, serverVariable.Extensions, serverVariable.Origin) } diff --git a/openapi3/server_test.go b/openapi3/server_test.go index c59b86e56..d4a5af954 100644 --- a/openapi3/server_test.go +++ b/openapi3/server_test.go @@ -1,8 +1,6 @@ package openapi3 import ( - "context" - "errors" "testing" "github.com/stretchr/testify/require" @@ -35,7 +33,7 @@ func TestServerParamValuesWithPath(t *testing.T) { "http://domain0.domain1.example.com/a/b-version/c/d": newServerMatch("/d", "domain0", "domain1", "b", "", ""), "http://domain0.domain1.example.com/a/1.0.0-version/c/d": newServerMatch("/d", "domain0", "domain1", "1.0.0", "", ""), } { - t.Run(input, testServerParamValues(t, server, input, expected)) + t.Run(input, testServerParamValues(server, input, expected)) } } @@ -46,7 +44,7 @@ func TestServerParamValuesNoPath(t *testing.T) { for input, expected := range map[string]*serverMatch{ "https://domain0.domain1.example.com/": newServerMatch("/", "domain0", "domain1"), } { - t.Run(input, testServerParamValues(t, server, input, expected)) + t.Run(input, testServerParamValues(server, input, expected)) } } @@ -62,33 +60,37 @@ func invalidServer() *Server { func TestServerValidation(t *testing.T) { tests := []struct { - name string - input *Server - expectedError error + name string + input *Server + expectedErrorMsg string // empty = expect no error }{ { "when no URL is provided", invalidServer(), - errors.New("value of url must be a non-empty string"), + "value of url must be a non-empty string", }, { "when a URL is provided", validServer(), - nil, + "", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := context.Background() + c := t.Context() validationErr := test.input.Validate(c) - require.Equal(t, test.expectedError, validationErr, "expected errors (or lack of) to match") + if test.expectedErrorMsg == "" { + require.NoError(t, validationErr) + } else { + require.EqualError(t, validationErr, test.expectedErrorMsg) + } }) } } -func testServerParamValues(t *testing.T, server *Server, input string, expected *serverMatch) func(*testing.T) { +func testServerParamValues(server *Server, input string, expected *serverMatch) func(*testing.T) { return func(t *testing.T) { args, remaining, ok := server.MatchRawURL(input) if expected == nil { @@ -192,7 +194,7 @@ func TestServersBasePath(t *testing.T) { }, } { t.Run(testcase.title, func(t *testing.T) { - err := testcase.servers.Validate(context.Background()) + err := testcase.servers.Validate(t.Context()) require.NoError(t, err) got, err := testcase.servers.BasePath() diff --git a/openapi3/stringmap.go b/openapi3/stringmap.go index 5cd68a5dc..f416899e7 100644 --- a/openapi3/stringmap.go +++ b/openapi3/stringmap.go @@ -19,8 +19,8 @@ func unmarshalStringMapP[V any](data []byte) (map[string]*V, error) { } result := make(map[string]*V, len(m)) - for k, v := range m { - value, err := deepCast[V](v) + for _, k := range componentNames(m) { + value, err := deepCast[V](m[k]) if err != nil { return nil, err } @@ -38,8 +38,8 @@ func unmarshalStringMap[V any](data []byte) (map[string]V, error) { } result := make(map[string]V, len(m)) - for k, v := range m { - value, err := deepCast[V](v) + for _, k := range componentNames(m) { + value, err := deepCast[V](m[k]) if err != nil { return nil, err } diff --git a/openapi3/tag.go b/openapi3/tag.go index 95524a443..c022a252d 100644 --- a/openapi3/tag.go +++ b/openapi3/tag.go @@ -3,7 +3,7 @@ package openapi3 import ( "context" "encoding/json" - "fmt" + "maps" ) // Tags is specified by OpenAPI/Swagger 3.0 standard. @@ -21,13 +21,14 @@ func (tags Tags) Get(name string) *Tag { // Validate returns an error if Tags does not comply with the OpenAPI spec. func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) for _, v := range tags { - if err := v.Validate(ctx); err != nil { + if err := me.emit(v.Validate(ctx)); err != nil { return err } } - return nil + return me.result() } // Tag is specified by OpenAPI/Swagger 3.0 standard. @@ -53,9 +54,7 @@ func (t Tag) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of Tag. func (t Tag) MarshalYAML() (any, error) { m := make(map[string]any, 3+len(t.Extensions)) - for k, v := range t.Extensions { - m[k] = v - } + maps.Copy(m, t.Extensions) if x := t.Name; x != "" { m["name"] = x } @@ -89,12 +88,14 @@ func (t *Tag) UnmarshalJSON(data []byte) error { // Validate returns an error if Tag does not comply with the OpenAPI spec. func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) + me := newErrCollector(ctx) if v := t.ExternalDocs; v != nil { - if err := v.Validate(ctx); err != nil { - return fmt.Errorf("invalid external docs: %w", err) + wrap := func(e error) error { return &SectionValidationError{Section: "external docs", Cause: e} } + if err := me.emitWrapped(wrap, v.Validate(ctx)); err != nil { + return err } } - return validateExtensions(ctx, t.Extensions) + return me.finalize(validateExtensions(ctx, t.Extensions, t.Origin)) } diff --git a/openapi3/testdata/apis_guru_openapi_directory/1password_com_events_1_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/1password_com_events_1_2_0_openapi_yaml__validate new file mode 100644 index 000000000..f39d8ab67 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/1password_com_events_1_2_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: request body "AuditEventsRequest": invalid example: example Continuing cursor: input matches more than one oneOf schemas diff --git a/openapi3/testdata/apis_guru_openapi_directory/1password_local_connect_1_5_7_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/1password_local_connect_1_5_7_openapi_yaml__validate new file mode 100644 index 000000000..7079a9194 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/1password_local_connect_1_5_7_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /vaults/{vaultUuid}/items/{itemUuid}: invalid operation PATCH: invalid example: example PatchItemAttr: Error at "/0/value": value must be an object +Schema: + { + "type": "object" + } + +Value: + true diff --git a/openapi3/testdata/apis_guru_openapi_directory/ably_io_platform_1_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ably_io_platform_1_1_0_openapi_yaml__validate new file mode 100644 index 000000000..941c6101e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ably_io_platform_1_1_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid components: parameter "filterLimit": parameter "limit" schema is invalid: invalid default: value must be an integer +Schema: + { + "default": "100", + "type": "integer" + } + +Value: + "100" diff --git a/openapi3/testdata/apis_guru_openapi_directory/ably_net_control_1_0_14_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ably_net_control_1_0_14_openapi_yaml__validate new file mode 100644 index 000000000..19eb01557 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ably_net_control_1_0_14_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "app_patch": invalid example: value must be a string +Schema: + { + "description": "The Firebase Cloud Messaging key.", + "example": false, + "nullable": true, + "type": "string" + } + +Value: + false diff --git a/openapi3/testdata/apis_guru_openapi_directory/abstractapi_com_geolocation_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/abstractapi_com_geolocation_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..cd5ef3e51 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/abstractapi_com_geolocation_1_0_0_openapi_yaml__validate @@ -0,0 +1,134 @@ +invalid paths: invalid path /v1/: invalid operation GET: invalid example: example 0: value must be an object +Schema: + { + "properties": { + "city": { + "type": "string" + }, + "city_geoname_id": { + "type": "integer" + }, + "connection": { + "properties": { + "autonomous_system_number": { + "type": "integer" + }, + "autonomous_system_organization": { + "type": "string" + }, + "connection_type": { + "type": "string" + }, + "isp_name": { + "type": "string" + }, + "organization_name": { + "type": "string" + } + }, + "type": "object" + }, + "continent": { + "type": "string" + }, + "continent_code": { + "type": "string" + }, + "continent_geoname_id": { + "type": "integer" + }, + "country": { + "type": "string" + }, + "country_code": { + "type": "string" + }, + "country_geoname_id": { + "type": "integer" + }, + "country_is_eu": { + "type": "boolean" + }, + "currency": { + "properties": { + "currency_code": { + "type": "string" + }, + "currency_name": { + "type": "string" + } + }, + "type": "object" + }, + "flag": { + "properties": { + "emoji": { + "type": "string" + }, + "png": { + "type": "string" + }, + "svg": { + "type": "string" + }, + "unicode": { + "type": "string" + } + }, + "type": "object" + }, + "ip_address": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "postal_code": { + "type": "string" + }, + "region": { + "type": "string" + }, + "region_geoname_id": { + "type": "integer" + }, + "region_iso_code": { + "type": "string" + }, + "security": { + "properties": { + "is_vpn": { + "type": "boolean" + } + }, + "type": "object" + }, + "timezone": { + "properties": { + "abbreviation": { + "type": "string" + }, + "current_time": { + "type": "string" + }, + "gmt_offset": { + "type": "integer" + }, + "is_dst": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + +Value: + "{\"ip_address\":\"195.154.25.40\",\"city\":\"Paris\",\"city_geoname_id\":2988507,\"region\":\"Île-de-France\",\"region_iso_code\":\"IDF\",\"region_geoname_id\":3012874,\"postal_code\":\"75008\",\"country\":\"France\",\"country_code\":\"FR\",\"country_geoname_id\":3017382,\"country_is_eu\":true,\"continent\":\"Europe\",\"continent_code\":\"EU\",\"continent_geoname_id\":6255148,\"longitude\":2.4075,\"latitude\":48.8323,\"security\":{\"is_vpn\":false},\"timezone\":{\"name\":\"Europe/Paris\",\"abbreviation\":\"CEST\",\"gmt_offset\":2,\"current_time\":\"15:42:18\",\"is_dst\":true},\"flag\":{\"emoji\":\"\u003cë\u003c÷\",\"unicode\":\"U+1F1EB U+1F1F7\",\"png\":\"https://static.abstractapi.com/country-flags/FR_flag.png\",\"svg\":\"https://static.abstractapi.com/country-flags/FR_flag.svg\"},\"currency\":{\"currency_name\":\"Euros\",\"currency_code\":\"EUR\"},\"connection\":{\"autonomous_system_number\":12876,\"autonomous_system_organization\":\"Online S.a.s.\",\"connection_type\":\"Corporate\",\"isp_name\":\"Online S.A.S.\",\"organization_name\":\"ONLINE\"}}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/adobe_com_aem_3_7_1_pre_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adobe_com_aem_3_7_1_pre_0_openapi_yaml__validate new file mode 100644 index 000000000..cfa1fa4b1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adobe_com_aem_3_7_1_pre_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "SamlConfigurationInfo": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_3_openapi_yaml__validate new file mode 100644 index 000000000..4103aab27 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_3_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /closeAccount: invalid operation POST: invalid example: example generic: validation failed due to: error at "/errorCode": at '/errorCode': got number, want string +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_4_openapi_yaml__validate new file mode 100644 index 000000000..4103aab27 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_4_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /closeAccount: invalid operation POST: invalid example: example generic: validation failed due to: error at "/errorCode": at '/errorCode': got number, want string +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_5_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_5_openapi_yaml__validate new file mode 100644 index 000000000..43443472b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_5_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /checkAccountHolder: invalid operation POST: invalid example: example basic: validation failed due to: error at "/tier": at '/tier': got string, want integer +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_6_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_6_openapi_yaml__validate new file mode 100644 index 000000000..43443472b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_AccountService_6_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /checkAccountHolder: invalid operation POST: invalid example: example basic: validation failed due to: error at "/tier": at '/tier': got string, want integer +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformConfigurationNotification_v1_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformConfigurationNotification_v1_1_openapi_yaml__validate new file mode 100644 index 000000000..782e47ddc --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformConfigurationNotification_v1_1_openapi_yaml__validate @@ -0,0 +1,126 @@ +invalid webhooks: webhook "balancePlatform.accountHolder.created": invalid operation POST: invalid example: example balancePlatform-accountHolder-created-lem-v3: Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/0/verificationErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 2902 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/0/verificationErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 2902 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/1/verificationErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 28037 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/1/verificationErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1703 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/2/verificationErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 28037 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/2/verificationErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1703 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 28189 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 2151 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/1/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 150 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/1/subErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 15016 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/1/subErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1500 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/1/subErrors/0/remediatingActions/1/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1501 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/2/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 28067 + | Error at "/data/accountHolder/capabilities/sendToTransferInstrument/problems/3/verificationErrors/2/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 2124 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_1_openapi_yaml__validate new file mode 100644 index 000000000..e85c2eaf5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_1_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /accountHolders: invalid operation POST: invalid example: example generic: Error at "/errorCode": value must be a string +Schema: + { + "description": "A code that identifies the problem type.", + "type": "string" + } + +Value: + 256 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_2_openapi_yaml__validate new file mode 100644 index 000000000..e85c2eaf5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_BalancePlatformService_2_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /accountHolders: invalid operation POST: invalid example: example generic: Error at "/errorCode": value must be a string +Schema: + { + "description": "A code that identifies the problem type.", + "type": "string" + } + +Value: + 256 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_37_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_37_openapi_yaml__load new file mode 100644 index 000000000..902879ef1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_37_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 4971: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_40_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_40_openapi_yaml__load new file mode 100644 index 000000000..01edf3ae1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_40_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5279: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_41_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_41_openapi_yaml__load new file mode 100644 index 000000000..6c4628c14 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_41_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5364: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_46_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_46_openapi_yaml__load new file mode 100644 index 000000000..253010369 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_46_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5365: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_49_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_49_openapi_yaml__load new file mode 100644 index 000000000..279a03b98 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_49_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5375: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_50_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_50_openapi_yaml__load new file mode 100644 index 000000000..426e3b15b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_50_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5433: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_51_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_51_openapi_yaml__load new file mode 100644 index 000000000..097ec6932 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_51_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5435: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_52_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_52_openapi_yaml__load new file mode 100644 index 000000000..6c1420bd4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_52_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5441: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_53_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_53_openapi_yaml__load new file mode 100644 index 000000000..6c1420bd4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_53_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5441: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_64_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_64_openapi_yaml__load new file mode 100644 index 000000000..6c1420bd4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_64_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5441: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_65_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_65_openapi_yaml__load new file mode 100644 index 000000000..4a31bfe54 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_65_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5456: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_66_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_66_openapi_yaml__load new file mode 100644 index 000000000..4a31bfe54 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_66_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5456: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_67_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_67_openapi_yaml__load new file mode 100644 index 000000000..e6fe6ddef --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_67_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 5410: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_68_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_68_openapi_yaml__load new file mode 100644 index 000000000..c0210c8b9 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_68_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 4685: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_69_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_69_openapi_yaml__load new file mode 100644 index 000000000..c8c9e7e90 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_69_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 4730: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_70_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_70_openapi_yaml__load new file mode 100644 index 000000000..a0b2b1c49 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_70_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 4776: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_v71_71_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_v71_71_openapi_yaml__load new file mode 100644 index 000000000..ba2850b0d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_CheckoutService_v71_71_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 4772: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_FundService_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_FundService_3_openapi_yaml__validate new file mode 100644 index 000000000..0d74fda13 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_FundService_3_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /debitAccountHolder: invalid operation POST: invalid example: example debit-account-holder: validation failed due to: error at "/submittedAsync": at '/submittedAsync': got string, want boolean +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_GrantService_v3_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_GrantService_v3_3_openapi_yaml__validate new file mode 100644 index 000000000..2eacb5eef --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_GrantService_v3_3_openapi_yaml__validate @@ -0,0 +1,43 @@ +invalid paths: invalid path /grants: invalid operation POST: invalid example: example requestGrant: Error at "/balances": value must be an object +Schema: + { + "description": "An object containing the details of the existing grant.", + "properties": { + "currency": { + "description": "The three-character [ISO currency code](https://docs.adyen.com/development-resources/currency-codes).", + "type": "string" + }, + "fee": { + "description": "Fee amount.", + "format": "int64", + "type": "integer" + }, + "principal": { + "description": "Principal amount.", + "format": "int64", + "type": "integer" + }, + "total": { + "description": "Total amount. A sum of principal amount and fee amount.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "principal", + "fee", + "total", + "currency" + ], + "type": "object" + } + +Value: + [ + { + "currency": "EUR", + "fee": 120000, + "principal": 1000000, + "total": 1120000 + } + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_1_openapi_yaml__validate new file mode 100644 index 000000000..54cabb2ba --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_1_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /documents: invalid operation POST: invalid example: validation failed due to: at '': got number, want string +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_2_openapi_yaml__validate new file mode 100644 index 000000000..54cabb2ba --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_2_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /documents: invalid operation POST: invalid example: validation failed due to: at '': got number, want string +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_3_openapi_yaml__validate new file mode 100644 index 000000000..54cabb2ba --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_LegalEntityService_3_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /documents: invalid operation POST: invalid example: validation failed due to: at '': got number, want string +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementNotificationService_v1_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementNotificationService_v1_1_openapi_yaml__validate new file mode 100644 index 000000000..5b041fdc7 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementNotificationService_v1_1_openapi_yaml__validate @@ -0,0 +1,63 @@ +invalid webhooks: webhook "merchant.updated": invalid operation POST: invalid example: example merchant-updated-with-errors: Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 28064 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 2123 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/1/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 130 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/1/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1300 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/1/subErrors/0/code": value must be a string +Schema: + { + "description": "The verification error code.", + "type": "string" + } + +Value: + 13000 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/1/subErrors/0/remediatingActions/0/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1300 + | Error at "/data/capabilities/receivePayments/problems/0/verificationErrors/1/subErrors/0/remediatingActions/1/code": value must be a string +Schema: + { + "description": "The remediating action code.", + "type": "string" + } + +Value: + 1301 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_1_openapi_yaml__validate new file mode 100644 index 000000000..a66c7bdb6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_1_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /terminals/scheduleActions: invalid operation POST: invalid example: example verification-error: Error at "/errorCode": value must be a string +Schema: + { + "description": "A code that identifies the problem type.", + "type": "string" + } + +Value: + 1029 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_v3_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_v3_3_openapi_yaml__validate new file mode 100644 index 000000000..a66c7bdb6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_ManagementService_v3_3_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /terminals/scheduleActions: invalid operation POST: invalid example: example verification-error: Error at "/errorCode": value must be a string +Schema: + { + "description": "A code that identifies the problem type.", + "type": "string" + } + +Value: + 1029 diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_3_openapi_yaml__validate new file mode 100644 index 000000000..3280c1b01 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_3_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid webhooks: webhook "/ACCOUNT_CLOSED": invalid operation POST: invalid example: example accountClosed: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_4_openapi_yaml__validate new file mode 100644 index 000000000..3280c1b01 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_4_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid webhooks: webhook "/ACCOUNT_CLOSED": invalid operation POST: invalid example: example accountClosed: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_5_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_5_openapi_yaml__validate new file mode 100644 index 000000000..3280c1b01 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_5_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid webhooks: webhook "/ACCOUNT_CLOSED": invalid operation POST: invalid example: example accountClosed: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_6_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_6_openapi_yaml__validate new file mode 100644 index 000000000..3280c1b01 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_MarketPayNotificationService_6_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid webhooks: webhook "/ACCOUNT_CLOSED": invalid operation POST: invalid example: example accountClosed: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_1_openapi_yaml__validate new file mode 100644 index 000000000..0caddb670 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_1_openapi_yaml__validate @@ -0,0 +1,28 @@ +invalid paths: invalid path /createNotificationConfiguration: invalid operation POST: invalid example: example basic: Error at "/configurationDetails/active": value must be a boolean +Schema: + { + "description": "Indicates whether the notification subscription is active.", + "type": "boolean" + } + +Value: + "true" + | Error at "/configurationDetails/sendActionHeader": value must be a boolean +Schema: + { + "deprecated": true, + "description": "Indicates whether an action header should be included.\n\u003eOnly applies to SOAP messages (as specified in messageFormat).", + "type": "boolean" + } + +Value: + "true" + | Error at "/submittedAsync": value must be a boolean +Schema: + { + "description": "Indicates whether the request is processed asynchronously. Depending on the request's platform settings, the following scenarios may be applied:\n* **true**: The request is queued and will be executed when the providing service is available in the order in which the requests are received.\n* **false**: The processing of the request is immediately attempted; it may result in an error if the providing service is unavailable.", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_2_openapi_yaml__validate new file mode 100644 index 000000000..0caddb670 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_2_openapi_yaml__validate @@ -0,0 +1,28 @@ +invalid paths: invalid path /createNotificationConfiguration: invalid operation POST: invalid example: example basic: Error at "/configurationDetails/active": value must be a boolean +Schema: + { + "description": "Indicates whether the notification subscription is active.", + "type": "boolean" + } + +Value: + "true" + | Error at "/configurationDetails/sendActionHeader": value must be a boolean +Schema: + { + "deprecated": true, + "description": "Indicates whether an action header should be included.\n\u003eOnly applies to SOAP messages (as specified in messageFormat).", + "type": "boolean" + } + +Value: + "true" + | Error at "/submittedAsync": value must be a boolean +Schema: + { + "description": "Indicates whether the request is processed asynchronously. Depending on the request's platform settings, the following scenarios may be applied:\n* **true**: The request is queued and will be executed when the providing service is available in the order in which the requests are received.\n* **false**: The processing of the request is immediately attempted; it may result in an error if the providing service is unavailable.", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_3_openapi_yaml__validate new file mode 100644 index 000000000..0caddb670 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_3_openapi_yaml__validate @@ -0,0 +1,28 @@ +invalid paths: invalid path /createNotificationConfiguration: invalid operation POST: invalid example: example basic: Error at "/configurationDetails/active": value must be a boolean +Schema: + { + "description": "Indicates whether the notification subscription is active.", + "type": "boolean" + } + +Value: + "true" + | Error at "/configurationDetails/sendActionHeader": value must be a boolean +Schema: + { + "deprecated": true, + "description": "Indicates whether an action header should be included.\n\u003eOnly applies to SOAP messages (as specified in messageFormat).", + "type": "boolean" + } + +Value: + "true" + | Error at "/submittedAsync": value must be a boolean +Schema: + { + "description": "Indicates whether the request is processed asynchronously. Depending on the request's platform settings, the following scenarios may be applied:\n* **true**: The request is queued and will be executed when the providing service is available in the order in which the requests are received.\n* **false**: The processing of the request is immediately attempted; it may result in an error if the providing service is unavailable.", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_4_openapi_yaml__validate new file mode 100644 index 000000000..0caddb670 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_NotificationConfigurationService_4_openapi_yaml__validate @@ -0,0 +1,28 @@ +invalid paths: invalid path /createNotificationConfiguration: invalid operation POST: invalid example: example basic: Error at "/configurationDetails/active": value must be a boolean +Schema: + { + "description": "Indicates whether the notification subscription is active.", + "type": "boolean" + } + +Value: + "true" + | Error at "/configurationDetails/sendActionHeader": value must be a boolean +Schema: + { + "deprecated": true, + "description": "Indicates whether an action header should be included.\n\u003eOnly applies to SOAP messages (as specified in messageFormat).", + "type": "boolean" + } + +Value: + "true" + | Error at "/submittedAsync": value must be a boolean +Schema: + { + "description": "Indicates whether the request is processed asynchronously. Depending on the request's platform settings, the following scenarios may be applied:\n* **true**: The request is queued and will be executed when the providing service is available in the order in which the requests are received.\n* **false**: The processing of the request is immediately attempted; it may result in an error if the providing service is unavailable.", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_25_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_25_openapi_yaml__load new file mode 100644 index 000000000..67d2323f8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_25_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 964: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_30_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_30_openapi_yaml__load new file mode 100644 index 000000000..14aaec114 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_30_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1158: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_40_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_40_openapi_yaml__load new file mode 100644 index 000000000..238ecf375 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_40_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1562: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_46_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_46_openapi_yaml__load new file mode 100644 index 000000000..238ecf375 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_46_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1562: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_49_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_49_openapi_yaml__load new file mode 100644 index 000000000..238ecf375 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_49_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1562: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_50_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_50_openapi_yaml__load new file mode 100644 index 000000000..9ff4f6619 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_50_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1575: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_51_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_51_openapi_yaml__load new file mode 100644 index 000000000..9cf7a1624 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_51_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1647: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_52_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_52_openapi_yaml__load new file mode 100644 index 000000000..9cf7a1624 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_52_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1647: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_64_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_64_openapi_yaml__load new file mode 100644 index 000000000..9cf7a1624 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_64_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1647: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_67_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_67_openapi_yaml__load new file mode 100644 index 000000000..9cf7a1624 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_67_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1647: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_68_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_68_openapi_yaml__load new file mode 100644 index 000000000..a113cdfeb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PaymentService_68_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 1808: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_46_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_46_openapi_yaml__load new file mode 100644 index 000000000..36ba354c8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_46_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 541: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_49_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_49_openapi_yaml__load new file mode 100644 index 000000000..36ba354c8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_PayoutService_49_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 541: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_RecurringService_18_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_RecurringService_18_openapi_yaml__validate new file mode 100644 index 000000000..0dce6316a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_RecurringService_18_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "RecurringDetailsRequest": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/adyen_com_TerminalAPI_v1_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_TerminalAPI_v1_1_openapi_yaml__validate new file mode 100644 index 000000000..856b2fb81 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/adyen_com_TerminalAPI_v1_1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AbortRequest": error parsing regexp: invalid repeat count: `{0,262144}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/agco_ats_com_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/agco_ats_com_v1_openapi_yaml__validate new file mode 100644 index 000000000..ba413e00d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/agco_ats_com_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AGCOPowerServices.Models.ECU": error parsing regexp: invalid repeat count: `{0,4096}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/airbyte_local_config_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/airbyte_local_config_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..a9c85bf4c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/airbyte_local_config_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AdvancedAuth": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_2_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_2_2_0_openapi_yaml__validate new file mode 100644 index 000000000..d72346952 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_2_2_0_openapi_yaml__validate @@ -0,0 +1,28 @@ +invalid components: schema "Error_400": invalid example: Error at "/errors/0/source": there must be at most 1 properties +Schema: + { + "description": "an object containing references to the source of the error", + "maxProperties": 1, + "properties": { + "example": { + "description": "a string indicating an example of the right value", + "type": "string" + }, + "parameter": { + "description": "a string indicating which URI query parameter caused the issue", + "type": "string" + }, + "pointer": { + "description": "a JSON Pointer [RFC6901] to the associated entity in the request document", + "type": "string" + } + }, + "title": "Issue_Source", + "type": "object" + } + +Value: + { + "example": "CDG", + "parameter": "airport" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_flight_price_analysis_1_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_flight_price_analysis_1_0_1_openapi_yaml__validate new file mode 100644 index 000000000..0df4e457c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_flight_price_analysis_1_0_1_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /analytics/itinerary-price-metrics: invalid operation GET: parameter "oneWay" schema is invalid: invalid default: value must be a boolean +Schema: + { + "default": "false", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_trip_parser_3_0_1_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_trip_parser_3_0_1_openapi_yaml__load new file mode 100644 index 000000000..10b861a73 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amadeus_com_amadeus_trip_parser_3_0_1_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: line 275: found a tab character where an indentation space is expected diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_AWSMigrationHub_2017_05_31_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_AWSMigrationHub_2017_05_31_openapi_yaml__validate new file mode 100644 index 000000000..d5f1c4929 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_AWSMigrationHub_2017_05_31_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "ApplicationId": error parsing regexp: invalid repeat count: `{1,1600}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_acm_2015_12_08_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_acm_2015_12_08_openapi_yaml__validate new file mode 100644 index 000000000..b48dd7e95 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_acm_2015_12_08_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "CertificateDetail": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_alexaforbusiness_2017_11_09_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_alexaforbusiness_2017_11_09_openapi_yaml__validate new file mode 100644 index 000000000..550733dbd --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_alexaforbusiness_2017_11_09_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AddressBook": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplify_2017_07_25_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplify_2017_07_25_openapi_yaml__validate new file mode 100644 index 000000000..715e8aca6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplify_2017_07_25_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "CreateDomainAssociationRequest": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplifyuibuilder_2021_08_11_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplifyuibuilder_2021_08_11_openapi_yaml__validate new file mode 100644 index 000000000..aba9d53d3 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_amplifyuibuilder_2021_08_11_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "TagKey": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apigateway_2015_07_09_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apigateway_2015_07_09_openapi_yaml__validate new file mode 100644 index 000000000..dfe7e19f4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apigateway_2015_07_09_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/restapis/{restapi_id}/resources/{resource_id}" and "/restapis/{restapi_id}/resources/{parent_id}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appconfigdata_2021_11_11_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appconfigdata_2021_11_11_openapi_yaml__validate new file mode 100644 index 000000000..c98a4dd42 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appconfigdata_2021_11_11_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "StartConfigurationSessionResponse": error parsing regexp: invalid repeat count: `{1,8192}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appflow_2020_08_23_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appflow_2020_08_23_openapi_yaml__validate new file mode 100644 index 000000000..aba9d53d3 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appflow_2020_08_23_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "TagKey": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appintegrations_2020_07_29_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appintegrations_2020_07_29_openapi_yaml__validate new file mode 100644 index 000000000..fcd8aa5dd --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appintegrations_2020_07_29_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Arn": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_application_autoscaling_2016_02_06_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_application_autoscaling_2016_02_06_openapi_yaml__validate new file mode 100644 index 000000000..f2eeb73e1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_application_autoscaling_2016_02_06_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "DescribeScalingPoliciesResponse": error parsing regexp: invalid character class range: `\p{Print}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_applicationcostprofiler_2020_09_10_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_applicationcostprofiler_2020_09_10_openapi_yaml__validate new file mode 100644 index 000000000..4bd96fdd9 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_applicationcostprofiler_2020_09_10_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "GetReportDefinitionResult": error parsing regexp: invalid or unsupported Perl syntax: `(?=` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apprunner_2020_05_15_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apprunner_2020_05_15_openapi_yaml__validate new file mode 100644 index 000000000..246147674 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_apprunner_2020_05_15_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AppRunnerResourceArn": error parsing regexp: invalid repeat count: `{1,1011}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appstream_2016_12_01_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appstream_2016_12_01_openapi_yaml__validate new file mode 100644 index 000000000..d89536f6b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appstream_2016_12_01_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AppBlock": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appsync_2017_07_25_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appsync_2017_07_25_openapi_yaml__validate new file mode 100644 index 000000000..aba9d53d3 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_appsync_2017_07_25_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "TagKey": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_auditmanager_2017_07_25_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_auditmanager_2017_07_25_openapi_yaml__validate new file mode 100644 index 000000000..aba9d53d3 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_auditmanager_2017_07_25_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "TagKey": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_autoscaling_plans_2018_01_06_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_autoscaling_plans_2018_01_06_openapi_yaml__validate new file mode 100644 index 000000000..cad19b68f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_autoscaling_plans_2018_01_06_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "CreateScalingPlanRequest": error parsing regexp: invalid character class range: `\p{Print}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_backup_2018_11_15_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_backup_2018_11_15_openapi_yaml__validate new file mode 100644 index 000000000..4e42eda9b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_backup_2018_11_15_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/audit/report-jobs/{reportPlanName}" and "/audit/report-jobs/{reportJobId}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_budgets_2016_10_20_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_budgets_2016_10_20_openapi_yaml__validate new file mode 100644 index 000000000..36f0ea768 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_budgets_2016_10_20_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Action": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_2018_05_01_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_2018_05_01_openapi_yaml__validate new file mode 100644 index 000000000..c2f8e8ff6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_2018_05_01_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AppInstance": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_identity_2021_04_20_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_identity_2021_04_20_openapi_yaml__validate new file mode 100644 index 000000000..c2f8e8ff6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_identity_2021_04_20_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AppInstance": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_meetings_2021_07_15_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_meetings_2021_07_15_openapi_yaml__validate new file mode 100644 index 000000000..80c0a40d8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_meetings_2021_07_15_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "CreateMeetingRequest": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_messaging_2021_05_15_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_messaging_2021_05_15_openapi_yaml__validate new file mode 100644 index 000000000..3b3d11143 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_chime_sdk_messaging_2021_05_15_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AssociateChannelFlowRequest": error parsing regexp: invalid repeat count: `{0,1023}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cleanrooms_2022_02_17_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cleanrooms_2022_02_17_openapi_yaml__validate new file mode 100644 index 000000000..f59a27ba9 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cleanrooms_2022_02_17_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Collaboration": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_codecatalyst_2022_09_28_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_codecatalyst_2022_09_28_openapi_yaml__validate new file mode 100644 index 000000000..d10c7930f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_codecatalyst_2022_09_28_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "CreateDevEnvironmentRequest": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cognito_idp_2016_04_18_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cognito_idp_2016_04_18_openapi_yaml__validate new file mode 100644 index 000000000..40733e408 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/amazonaws_com_cognito_idp_2016_04_18_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "DescribeUserPoolDomainResponse": error parsing regexp: invalid named capture: `(?",\s]+$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/orthanc_server_com_1_12_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/orthanc_server_com_1_12_0_openapi_yaml__validate new file mode 100644 index 000000000..903de1e1a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/orthanc_server_com_1_12_0_openapi_yaml__validate @@ -0,0 +1,128 @@ +invalid paths: invalid path /series/{id}/ordered-slices: invalid operation GET: invalid example: Error at "/SlicesShort/0": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "fa1fc64f-e7a051a7-c1233b31-e19bca08-54629531", + 0, + 1 + ] + | Error at "/SlicesShort/1": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "f5701efb-98170697-404d15f6-59baf69e-4e8ddfae", + 0, + 1 + ] + | Error at "/SlicesShort/2": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "3b2a215c-2560d4b0-e3730c07-87d3fa7d-3fef44ed", + 0, + 1 + ] + | Error at "/SlicesShort/3": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "6059d07c-02ae8c74-9436dc7c-007b4d5d-4c770a30", + 0, + 1 + ] + | Error at "/SlicesShort/4": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "99264275-213c3190-73251bdc-97ccfdb4-1f9656d9", + 0, + 1 + ] + | Error at "/SlicesShort/5": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "40df6ac1-5ec86316-035ff3c1-07e5c8a8-f6cbd37c", + 0, + 1 + ] + | Error at "/SlicesShort/6": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "08ff3a1a-c8fb57d9-4e24d9cb-2ed22b80-0ff7461e", + 0, + 1 + ] + | Error at "/SlicesShort/7": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "6fe353dd-544ce7af-58ce22dd-ab67370c-873330fc", + 0, + 1 + ] + | Error at "/SlicesShort/8": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "3ad70bbe-bc51faff-860461d2-44745ccf-0ebbbbc8", + 0, + 1 + ] + | Error at "/SlicesShort/9": value must be an object +Schema: + { + "type": "object" + } + +Value: + [ + "9058e01a-31a7d982-1691e575-a8607d94-29a9aaca", + 0, + 1 + ] + | Error at "/SlicesShort/10": value must be an object +Schema: + { + "type": "object" + } + +Value: + "..." diff --git a/openapi3/testdata/apis_guru_openapi_directory/osf_io_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/osf_io_2_0_openapi_yaml__validate new file mode 100644 index 000000000..c1968f11a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/osf_io_2_0_openapi_yaml__validate @@ -0,0 +1,230 @@ +invalid components: request body "nodes_contributors_createBody": invalid example: Error at "/type": property "type" is missing +Schema: + { + "example": { + "data": { + "attributes": {}, + "relationships": { + "user": { + "data": { + "id": "guid0", + "type": "users" + } + } + }, + "type": "contributors" + } + }, + "properties": { + "attributes": { + "description": "The properties of the contributor entity.", + "properties": { + "bibliographic": { + "description": "Whether or not the contributor will be included in citations for the node. The default value is true.", + "type": "boolean" + }, + "index": { + "description": "The position of this contributor in the list of project contributors and in project citations.", + "type": "integer" + }, + "permission": { + "description": "The permission level of the contributor. The default value is 'write'.", + "enum": [ + "read", + "write", + "admin" + ], + "type": "string" + }, + "unregistered_contributor": { + "description": "The assigned name of the contributor if the contributor has not yet claimed their account.", + "readOnly": true, + "type": "string" + } + }, + "title": "Attributes", + "type": "object" + }, + "id": { + "description": "The identifier of the contributor entity. Contributor identifiers have the form {node_id}-{user_id}.", + "readOnly": true, + "type": "string" + }, + "links": { + "description": "URLs to alternative representations of the contributor entity.", + "properties": { + "self": { + "description": "A link to the the canonical API endpoint for the contributor.", + "format": "URL", + "readOnly": true, + "type": "string" + } + }, + "readOnly": true, + "title": "Links", + "type": "object" + }, + "relationships": { + "description": "URLs to other entities or entity collections that have a relationship to the contributor entity.", + "properties": { + "node": { + "description": "A relationship to the node that was created for the preprint, or from which the preprint was created.", + "readOnly": true, + "type": "string" + }, + "user": { + "description": "A relationship to the file that is designated as the preprint's primary file, or the manuscript of the preprint.", + "type": "string" + } + }, + "required": [ + "node", + "user" + ], + "title": "Relationships", + "type": "object" + }, + "type": { + "description": "The type identifier of the contributor entity (`contributors`).", + "readOnly": true, + "type": "string" + } + }, + "required": [ + "type", + "relationships" + ], + "title": "Contributor", + "type": "object" + } + +Value: + { + "data": { + "attributes": {}, + "relationships": { + "user": { + "data": { + "id": "guid0", + "type": "users" + } + } + }, + "type": "contributors" + } + } + | Error at "/relationships": property "relationships" is missing +Schema: + { + "example": { + "data": { + "attributes": {}, + "relationships": { + "user": { + "data": { + "id": "guid0", + "type": "users" + } + } + }, + "type": "contributors" + } + }, + "properties": { + "attributes": { + "description": "The properties of the contributor entity.", + "properties": { + "bibliographic": { + "description": "Whether or not the contributor will be included in citations for the node. The default value is true.", + "type": "boolean" + }, + "index": { + "description": "The position of this contributor in the list of project contributors and in project citations.", + "type": "integer" + }, + "permission": { + "description": "The permission level of the contributor. The default value is 'write'.", + "enum": [ + "read", + "write", + "admin" + ], + "type": "string" + }, + "unregistered_contributor": { + "description": "The assigned name of the contributor if the contributor has not yet claimed their account.", + "readOnly": true, + "type": "string" + } + }, + "title": "Attributes", + "type": "object" + }, + "id": { + "description": "The identifier of the contributor entity. Contributor identifiers have the form {node_id}-{user_id}.", + "readOnly": true, + "type": "string" + }, + "links": { + "description": "URLs to alternative representations of the contributor entity.", + "properties": { + "self": { + "description": "A link to the the canonical API endpoint for the contributor.", + "format": "URL", + "readOnly": true, + "type": "string" + } + }, + "readOnly": true, + "title": "Links", + "type": "object" + }, + "relationships": { + "description": "URLs to other entities or entity collections that have a relationship to the contributor entity.", + "properties": { + "node": { + "description": "A relationship to the node that was created for the preprint, or from which the preprint was created.", + "readOnly": true, + "type": "string" + }, + "user": { + "description": "A relationship to the file that is designated as the preprint's primary file, or the manuscript of the preprint.", + "type": "string" + } + }, + "required": [ + "node", + "user" + ], + "title": "Relationships", + "type": "object" + }, + "type": { + "description": "The type identifier of the contributor entity (`contributors`).", + "readOnly": true, + "type": "string" + } + }, + "required": [ + "type", + "relationships" + ], + "title": "Contributor", + "type": "object" + } + +Value: + { + "data": { + "attributes": {}, + "relationships": { + "user": { + "data": { + "id": "guid0", + "type": "users" + } + } + }, + "type": "contributors" + } + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_abuse_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_abuse_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_abuse_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_aftermarket_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_aftermarket_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_aftermarket_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_agreements_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_agreements_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_agreements_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_certificates_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_certificates_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_certificates_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_countries_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_countries_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_countries_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_domains_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_domains_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..2ad851ea8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_domains_1_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "DomainNotification": invalid default: value must be an object +Schema: + { + "default": "", + "description": "The notification data for the given type as specifed by GET /v2/customers/{customerId}/domains/notifications/schema", + "type": "object" + } + +Value: + "" diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_orders_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_orders_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_orders_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_shoppers_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_shoppers_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_shoppers_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_subscriptions_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_subscriptions_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..7caff2ce1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ote_godaddy_com_subscriptions_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid info: value of title must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/oxforddictionaries_com_1_11_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/oxforddictionaries_com_1_11_0_openapi_yaml__validate new file mode 100644 index 000000000..733fad3f6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/oxforddictionaries_com_1_11_0_openapi_yaml__validate @@ -0,0 +1,13 @@ +invalid paths: invalid path /search/{source_lang}: invalid operation GET: parameter "prefix" schema is invalid: invalid default: value must be a boolean +Schema: + { + "default": "false", + "enum": [ + "false", + "true" + ], + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/pandascore_co_2_23_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pandascore_co_2_23_1_openapi_yaml__validate new file mode 100644 index 000000000..a3b23feea --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pandascore_co_2_23_1_openapi_yaml__validate @@ -0,0 +1,54715 @@ +invalid components: response "AdditionIncidents": invalid example: example /additions?page[size]=1: Error at "/0": doesn't match schema due to: Error at "/object": property "begin_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "detailed_stats" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "draw" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "end_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "forfeit" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "game_advantage" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live_embed_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "match_type" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "number_of_games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "official_stream_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "opponents" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "original_scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "rescheduled" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "results" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "status" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams_list" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/videogame": doesn't match schema due to: Error at "/id": value is not one of the allowed values [1] +Schema: + { + "enum": [ + 1 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["LoL"] +Schema: + { + "enum": [ + "LoL" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["league-of-legends"] +Schema: + { + "enum": [ + "league-of-legends" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 1 + ] + }, + "name": { + "enum": [ + "LoL" + ] + }, + "slug": { + "enum": [ + "league-of-legends" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [3] +Schema: + { + "enum": [ + 3 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["CS:GO"] +Schema: + { + "enum": [ + "CS:GO" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["cs-go"] +Schema: + { + "enum": [ + "cs-go" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 3 + ] + }, + "name": { + "enum": [ + "CS:GO" + ] + }, + "slug": { + "enum": [ + "cs-go" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 4 + ] + }, + "name": { + "enum": [ + "Dota 2" + ] + }, + "slug": { + "enum": [ + "dota-2" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [14] +Schema: + { + "enum": [ + 14 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["Overwatch"] +Schema: + { + "enum": [ + "Overwatch" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["ow"] +Schema: + { + "enum": [ + "ow" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 14 + ] + }, + "name": { + "enum": [ + "Overwatch" + ] + }, + "slug": { + "enum": [ + "ow" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [20] +Schema: + { + "enum": [ + 20 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["PUBG"] +Schema: + { + "enum": [ + "PUBG" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["pubg"] +Schema: + { + "enum": [ + "pubg" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 20 + ] + }, + "name": { + "enum": [ + "PUBG" + ] + }, + "slug": { + "enum": [ + "pubg" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [22] +Schema: + { + "enum": [ + 22 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["Rocket League"] +Schema: + { + "enum": [ + "Rocket League" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["rl"] +Schema: + { + "enum": [ + "rl" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 22 + ] + }, + "name": { + "enum": [ + "Rocket League" + ] + }, + "slug": { + "enum": [ + "rl" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [23] +Schema: + { + "enum": [ + 23 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["Call of Duty"] +Schema: + { + "enum": [ + "Call of Duty" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["cod-mw"] +Schema: + { + "enum": [ + "cod-mw" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 23 + ] + }, + "name": { + "enum": [ + "Call of Duty" + ] + }, + "slug": { + "enum": [ + "cod-mw" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [24] +Schema: + { + "enum": [ + 24 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["Rainbow 6 Siege"] +Schema: + { + "enum": [ + "Rainbow 6 Siege" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["r6-siege"] +Schema: + { + "enum": [ + "r6-siege" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 24 + ] + }, + "name": { + "enum": [ + "Rainbow 6 Siege" + ] + }, + "slug": { + "enum": [ + "r6-siege" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [25] +Schema: + { + "enum": [ + 25 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["FIFA"] +Schema: + { + "enum": [ + "FIFA" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["fifa"] +Schema: + { + "enum": [ + "fifa" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 25 + ] + }, + "name": { + "enum": [ + "FIFA" + ] + }, + "slug": { + "enum": [ + "fifa" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + Or Error at "/id": value is not one of the allowed values [26] +Schema: + { + "enum": [ + 26 + ] + } + +Value: + 4 + | Error at "/name": value is not one of the allowed values ["Valorant"] +Schema: + { + "enum": [ + "Valorant" + ] + } + +Value: + "Dota 2" + | Error at "/slug": value is not one of the allowed values ["valorant"] +Schema: + { + "enum": [ + "valorant" + ] + } + +Value: + "dota-2" + | Error at "/current_version": property "current_version" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "current_version": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameVersion" + } + ], + "title": "VideogameVersion" + }, + "id": { + "enum": [ + 26 + ] + }, + "name": { + "enum": [ + "Valorant" + ] + }, + "slug": { + "enum": [ + "valorant" + ] + } + }, + "required": [ + "current_version", + "id", + "name", + "slug" + ], + "type": "object" + } + +Value: + { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + } + | Error at "/object": property "videogame_version" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/image_url": property "image_url" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/series": property "series" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/url": property "url" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/components/schemas/LeagueID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueImageURL" + } + ], + "title": "LeagueImageURL" + }, + "modified_at": { + "$ref": "#/components/schemas/LeagueModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/LeagueName" + }, + "series": { + "$ref": "#/components/schemas/BaseSeries" + }, + "slug": { + "$ref": "#/components/schemas/LeagueSlug" + }, + "url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/LeagueURL" + } + ], + "title": "LeagueURL" + }, + "videogame": { + "$ref": "#/components/schemas/LeagueVideogame" + } + }, + "required": [ + "id", + "image_url", + "modified_at", + "name", + "series", + "slug", + "url", + "videogame" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/type": value is not one of the allowed values ["league"] +Schema: + { + "enum": [ + "league" + ] + } + +Value: + "match" + Or Error at "/object/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/game_advantage": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/0/begin_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/0/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/0/length": doesn't match schema due to: Value is not nullable +Schema: + { + "minimum": 0, + "type": "integer" + } + +Value: + null + | Error at "/object/games/0/video_url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/games/0/winner/id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/0/winner/type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/0/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/1/begin_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/1/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/1/length": doesn't match schema due to: Value is not nullable +Schema: + { + "minimum": 0, + "type": "integer" + } + +Value: + null + | Error at "/object/games/1/video_url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/games/1/winner/id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/1/winner/type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/1/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/2/begin_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/2/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/2/length": doesn't match schema due to: Value is not nullable +Schema: + { + "minimum": 0, + "type": "integer" + } + +Value: + null + | Error at "/object/games/2/video_url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/games/2/winner/id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/2/winner/type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/2/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/3/begin_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/3/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/3/length": doesn't match schema due to: Value is not nullable +Schema: + { + "minimum": 0, + "type": "integer" + } + +Value: + null + | Error at "/object/games/3/video_url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/games/3/winner/id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/3/winner/type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/3/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/4/begin_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/4/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/games/4/length": doesn't match schema due to: Value is not nullable +Schema: + { + "minimum": 0, + "type": "integer" + } + +Value: + null + | Error at "/object/games/4/video_url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/games/4/winner/id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/games/4/winner/type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/games/4/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/league/url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/live/opens_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/live/url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object/serie/description": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/serie/name": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/season": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/serie/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object/tournament/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/videogame_version": doesn't match schema due to: Value is not nullable +Schema: + { + "additionalProperties": false, + "properties": { + "current": { + "$ref": "#/components/schemas/VideogameVersionIsCurrent" + }, + "name": { + "$ref": "#/components/schemas/VideogameVersion" + } + }, + "required": [ + "current", + "name" + ], + "type": "object" + } + +Value: + null + | Error at "/object/winner": doesn't match schema due to: doesn't match schema due to: Value is not nullable +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "birth_year", + "birthday", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + null + Or Value is not nullable +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "id", + "image_url", + "location", + "modified_at", + "name", + "slug" + ], + "type": "object" + } + +Value: + null + | Error at "/object/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + Or Error at "/object": property "begin_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "detailed_stats" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "draw" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "end_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "forfeit" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "game_advantage" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live_embed_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "match_type" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "modified_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "number_of_games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "official_stream_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "opponents" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "original_scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "rescheduled" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "results" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "status" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams_list" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame_version" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/current_team": property "current_team" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/current_videogame": property "current_videogame" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/first_name": property "first_name" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/hometown": property "hometown" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/image_url": property "image_url" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/last_name": property "last_name" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/nationality": property "nationality" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/role": property "role" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "birth_year": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthYear" + } + ], + "title": "PlayerBirthYear" + }, + "birthday": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerBirthday" + } + ], + "title": "PlayerBirthday" + }, + "current_team": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/BaseTeam" + } + ], + "title": "BaseTeam" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "first_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerFirstName" + } + ], + "title": "PlayerFirstName" + }, + "hometown": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerHometown" + } + ], + "title": "PlayerHometown" + }, + "id": { + "$ref": "#/components/schemas/PlayerID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerImageURL" + } + ], + "title": "PlayerImageURL" + }, + "last_name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerLastName" + } + ], + "title": "PlayerLastName" + }, + "name": { + "$ref": "#/components/schemas/PlayerName" + }, + "nationality": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerNationality" + } + ], + "title": "PlayerNationality" + }, + "role": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerRoleSlug" + } + ], + "title": "PlayerRoleSlug" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/PlayerSlug" + } + ], + "title": "PlayerSlug" + } + }, + "required": [ + "current_team", + "current_videogame", + "first_name", + "hometown", + "id", + "image_url", + "last_name", + "name", + "nationality", + "role", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/type": value is not one of the allowed values ["player"] +Schema: + { + "enum": [ + "player" + ] + } + +Value: + "match" + Or Error at "/object": property "detailed_stats" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "draw" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object": property "forfeit" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "game_advantage" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "games" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/league/url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object": property "live" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live_embed_url" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "match_type" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "number_of_games" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "official_stream_url" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "opponents" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "original_scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "rescheduled" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "results" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie_id" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "status" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams_list" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament_id" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame_version" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner" is unsupported +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/description": property "description" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/full_name": property "full_name" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/season": property "season" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/tier": property "tier" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/tournaments": property "tournaments" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/videogame_title": property "videogame_title" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/winner_type": property "winner_type" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/year": property "year" is missing +Schema: + { + "additionalProperties": false, + "description": "A serie, an occurrence of a league event", + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieBeginAt" + } + ], + "title": "SerieBeginAt" + }, + "description": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieDescription" + } + ], + "title": "SerieDescription" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieEndAt" + } + ], + "title": "SerieEndAt" + }, + "full_name": { + "$ref": "#/components/schemas/SerieFullName" + }, + "id": { + "$ref": "#/components/schemas/SerieID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "modified_at": { + "$ref": "#/components/schemas/SerieModifiedAt" + }, + "name": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieName" + } + ], + "title": "SerieName" + }, + "season": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieSeason" + } + ], + "title": "SerieSeason" + }, + "slug": { + "$ref": "#/components/schemas/SerieSlug" + }, + "tier": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/SerieTier" + } + ], + "title": "SerieTier" + }, + "tournaments": { + "$ref": "#/components/schemas/BaseTournaments" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "videogame_title": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/VideogameTitle" + } + ], + "title": "VideogameTitle" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + }, + "year": { + "$ref": "#/components/schemas/SerieYear" + } + }, + "required": [ + "begin_at", + "description", + "end_at", + "full_name", + "id", + "league", + "league_id", + "modified_at", + "name", + "season", + "slug", + "tier", + "tournaments", + "videogame", + "videogame_title", + "winner_id", + "winner_type", + "year" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/type": value is not one of the allowed values ["serie"] +Schema: + { + "enum": [ + "serie" + ] + } + +Value: + "match" + Or Error at "/object": property "begin_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "detailed_stats" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "draw" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "end_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "forfeit" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "game_advantage" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "league_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live_embed_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "match_type" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "number_of_games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "official_stream_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "opponents" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "original_scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "rescheduled" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "results" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "serie_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "status" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams_list" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame_version" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/acronym": property "acronym" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/current_videogame": property "current_videogame" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/image_url": property "image_url" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/location": property "location" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/players": property "players" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "acronym": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamAcronym" + } + ], + "title": "TeamAcronym" + }, + "current_videogame": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/CurrentVideogame" + } + ], + "title": "CurrentVideogame" + }, + "id": { + "$ref": "#/components/schemas/TeamID" + }, + "image_url": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamImageURL" + } + ], + "title": "TeamImageURL" + }, + "location": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamLocation" + } + ], + "title": "TeamLocation" + }, + "modified_at": { + "$ref": "#/components/schemas/TeamModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TeamName" + }, + "players": { + "$ref": "#/components/schemas/BasePlayers" + }, + "slug": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TeamSlug" + } + ], + "title": "TeamSlug" + } + }, + "required": [ + "acronym", + "current_videogame", + "id", + "image_url", + "location", + "modified_at", + "name", + "players", + "slug" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/type": value is not one of the allowed values ["team"] +Schema: + { + "enum": [ + "team" + ] + } + +Value: + "match" + Or Error at "/object": property "detailed_stats" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "draw" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object": property "forfeit" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "game_advantage" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/league/url": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "uri", + "type": "string" + } + +Value: + null + | Error at "/object": property "live" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "live_embed_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "match_type" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "number_of_games" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "official_stream_url" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "opponents" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "original_scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "rescheduled" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "results" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "scheduled_at" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/serie/description": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/end_at": doesn't match schema due to: Value is not nullable +Schema: + { + "format": "date-time", + "minLength": 1, + "type": "string" + } + +Value: + null + | Error at "/object/serie/name": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/season": doesn't match schema due to: Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/object/serie/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/serie/winner_type": doesn't match schema due to: value is not one of the allowed values ["Player","Team"] +Schema: + { + "enum": [ + "Player", + "Team" + ], + "type": "string" + } + +Value: + null + | Error at "/object": property "status" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "streams_list" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "tournament_id" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "videogame_version" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object": property "winner" is unsupported +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/winner_id": doesn't match schema due to: doesn't match any schema from "anyOf" +Schema: + { + "anyOf": [ + { + "$ref": "#/components/schemas/PlayerID" + }, + { + "$ref": "#/components/schemas/TeamID" + } + ] + } + +Value: + null + | Error at "/object/expected_roster": property "expected_roster" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/live_supported": property "live_supported" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/matches": property "matches" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/prizepool": property "prizepool" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/teams": property "teams" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/object/winner_type": property "winner_type" is missing +Schema: + { + "additionalProperties": false, + "properties": { + "begin_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentBeginAt" + } + ], + "title": "TournamentBeginAt" + }, + "end_at": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentEndAt" + } + ], + "title": "TournamentEndAt" + }, + "expected_roster": { + "$ref": "#/components/schemas/TournamentRosterItems" + }, + "id": { + "$ref": "#/components/schemas/TournamentID" + }, + "league": { + "$ref": "#/components/schemas/BaseLeague" + }, + "league_id": { + "$ref": "#/components/schemas/LeagueID" + }, + "live_supported": { + "$ref": "#/components/schemas/TournamentLiveSupported" + }, + "matches": { + "$ref": "#/components/schemas/BaseMatches" + }, + "modified_at": { + "$ref": "#/components/schemas/TournamentModifiedAt" + }, + "name": { + "$ref": "#/components/schemas/TournamentName" + }, + "prizepool": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/TournamentPrizepool" + } + ], + "title": "TournamentPrizepool" + }, + "serie": { + "$ref": "#/components/schemas/BaseSerie" + }, + "serie_id": { + "$ref": "#/components/schemas/SerieID" + }, + "slug": { + "$ref": "#/components/schemas/TournamentSlug" + }, + "teams": { + "$ref": "#/components/schemas/BaseTeams" + }, + "videogame": { + "$ref": "#/components/schemas/CurrentVideogame" + }, + "winner_id": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentID" + } + ], + "title": "OpponentID" + }, + "winner_type": { + "allOf": [ + { + "nullable": true + }, + { + "$ref": "#/components/schemas/OpponentType" + } + ], + "title": "OpponentType" + } + }, + "required": [ + "begin_at", + "end_at", + "expected_roster", + "id", + "league", + "league_id", + "live_supported", + "matches", + "modified_at", + "name", + "prizepool", + "serie", + "serie_id", + "slug", + "teams", + "videogame", + "winner_id", + "winner_type" + ], + "type": "object" + } + +Value: + { + "begin_at": "2021-04-24T16:00:00Z", + "detailed_stats": true, + "draw": false, + "end_at": null, + "forfeit": false, + "game_advantage": null, + "games": [ + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674134, + "length": null, + "match_id": 591022, + "position": 1, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674135, + "length": null, + "match_id": 591022, + "position": 2, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674136, + "length": null, + "match_id": 591022, + "position": 3, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674137, + "length": null, + "match_id": 591022, + "position": 4, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + }, + { + "begin_at": null, + "complete": false, + "detailed_stats": true, + "end_at": null, + "finished": false, + "forfeit": false, + "id": 674138, + "length": null, + "match_id": 591022, + "position": 5, + "status": "not_started", + "video_url": null, + "winner": { + "id": null, + "type": null + }, + "winner_type": null + } + ], + "id": 591022, + "league": { + "id": 4562, + "image_url": "https://cdn.dev.pandascore.co/images/league/image/4562/positive-fire-games1615995754399-logo-1.png", + "modified_at": "2021-04-22T10:15:12Z", + "name": "Positive Fire Games", + "slug": "dota-2-positive-fire-games", + "url": null + }, + "league_id": 4562, + "live": { + "opens_at": null, + "supported": false, + "url": null + }, + "live_embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "match_type": "best_of", + "modified_at": "2021-04-22T23:45:50Z", + "name": "Grand Final: TBD vs TBD", + "number_of_games": 5, + "official_stream_url": "https://www.twitch.tv/bufistudio_ru", + "opponents": [], + "original_scheduled_at": "2021-04-24T16:00:00Z", + "rescheduled": false, + "results": [], + "scheduled_at": "2021-04-24T16:00:00Z", + "serie": { + "begin_at": "2021-04-12T10:00:00Z", + "description": null, + "end_at": null, + "full_name": "2021", + "id": 3538, + "league_id": 4562, + "modified_at": "2021-04-12T07:20:33Z", + "name": null, + "season": null, + "slug": "dota-2-positive-fire-games-2021", + "tier": "d", + "winner_id": null, + "winner_type": null, + "year": 2021 + }, + "serie_id": 3538, + "slug": "2021-04-24-69c221a9-a725-451e-a61d-e8f4915a67d5", + "status": "not_started", + "streams": { + "english": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + "official": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + }, + "russian": { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + }, + "streams_list": [ + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_eu", + "language": "en", + "main": false, + "official": false, + "raw_url": "https://www.twitch.tv/bufistudio_eu" + }, + { + "embed_url": "https://player.twitch.tv/?channel=bufistudio_ru", + "language": "ru", + "main": true, + "official": true, + "raw_url": "https://www.twitch.tv/bufistudio_ru" + } + ], + "tournament": { + "begin_at": "2021-04-19T10:00:00Z", + "end_at": "2021-04-24T22:00:00Z", + "id": 5928, + "league_id": 4562, + "live_supported": false, + "modified_at": "2021-04-22T13:14:31Z", + "name": "Playoffs", + "prizepool": "10000 United States Dollar", + "serie_id": 3538, + "slug": "dota-2-positive-fire-games-2021-playoffs", + "winner_id": null, + "winner_type": "Team" + }, + "tournament_id": 5928, + "videogame": { + "id": 4, + "name": "Dota 2", + "slug": "dota-2" + }, + "videogame_version": null, + "winner": null, + "winner_id": null + } + | Error at "/type": value is not one of the allowed values ["tournament"] +Schema: + { + "enum": [ + "tournament" + ] + } + +Value: + "match" diff --git a/openapi3/testdata/apis_guru_openapi_directory/pay1_de_link_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pay1_de_link_v1_openapi_yaml__validate new file mode 100644 index 000000000..89e5fff52 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pay1_de_link_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: security scheme "createAuth": security scheme of type 'http' has invalid 'scheme' value "payone-hmac-sha256" diff --git a/openapi3/testdata/apis_guru_openapi_directory/paypi_dev_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/paypi_dev_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..feee15598 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/paypi_dev_1_0_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /checkCode: invalid operation POST: invalid default: value must be a string +Schema: + { + "default": 123456, + "type": "string" + } + +Value: + 123456 diff --git a/openapi3/testdata/apis_guru_openapi_directory/pdfbroker_io_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pdfbroker_io_v1_openapi_yaml__validate new file mode 100644 index 000000000..9f17ac0cb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pdfbroker_io_v1_openapi_yaml__validate @@ -0,0 +1,24 @@ +invalid paths: invalid path /api/pdf: invalid operation GET: invalid example: value must be an object +Schema: + { + "additionalProperties": false, + "properties": { + "description": { + "nullable": true, + "type": "string" + }, + "errorMessage": { + "description": "If any error occurs the message will be displayed in here", + "nullable": true, + "type": "string" + }, + "statusCode": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + +Value: + "{\r\n \"description\": \"Always empty in the public response, used for internal error transport to logs\",\r\n \"statusCode\": 400,\r\n \"errorMessage\": \"The error message provided to client\"\r\n}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/pdfgeneratorapi_com_3_1_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pdfgeneratorapi_com_3_1_1_openapi_yaml__validate new file mode 100644 index 000000000..678cfcb2d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pdfgeneratorapi_com_3_1_1_openapi_yaml__validate @@ -0,0 +1,13 @@ +invalid components: response "error403": invalid example: value is not one of the allowed values ["Your account has exceeded the monthly document generation limit."] +Schema: + { + "description": "Error description", + "enum": [ + "Your account has exceeded the monthly document generation limit." + ], + "example": "Access not granted", + "type": "string" + } + +Value: + "Access not granted" diff --git a/openapi3/testdata/apis_guru_openapi_directory/personio_de_personnel_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/personio_de_personnel_1_0_openapi_yaml__validate new file mode 100644 index 000000000..63e5f7d6b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/personio_de_personnel_1_0_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "NewAttendancePeriodRequest": invalid example: Error at "/attendances/0/comment": value must be a string +Schema: + { + "description": "Optional comment", + "type": "string" + } + +Value: + { + "$ref": "#/components/schemas/UpdateAttendancePeriodRequest/example/comment" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/placekit_co_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/placekit_co_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..471b94879 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/placekit_co_1_0_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid components: response "422": invalid example: Error at "/0/value": value must be a string +Schema: + { + "type": "string" + } + +Value: + 42 diff --git a/openapi3/testdata/apis_guru_openapi_directory/plaid_com_2020_09_14_1_345_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/plaid_com_2020_09_14_1_345_1_openapi_yaml__validate new file mode 100644 index 000000000..e9ec8ef81 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/plaid_com_2020_09_14_1_345_1_openapi_yaml__validate @@ -0,0 +1,314 @@ +invalid paths: invalid path /asset_report/get: invalid operation POST: invalid example: example example-1: Error at "/report/items/0/accounts/0": doesn't match schema due to: Error at "/balances/limit": property "limit" is missing +Schema: + { + "additionalProperties": true, + "description": "A set of fields describing the balance for an account. Balance information may be cached unless the balance object was returned by `/accounts/balance/get`.", + "properties": { + "available": { + "description": "The amount of funds available to be withdrawn from the account, as determined by the financial institution.\n\nFor `credit`-type accounts, the `available` balance typically equals the `limit` less the `current` balance, less any pending outflows plus any pending inflows.\n\nFor `depository`-type accounts, the `available` balance typically equals the `current` balance less any pending outflows plus any pending inflows. For `depository`-type accounts, the `available` balance does not include the overdraft limit.\n\nFor `investment`-type accounts (or `brokerage`-type accounts for API versions 2018-05-22 and earlier), the `available` balance is the total cash available to withdraw as presented by the institution.\n\nNote that not all institutions calculate the `available` balance. In the event that `available` balance is unavailable, Plaid will return an `available` balance value of `null`.\n\nAvailable balance may be cached and is not guaranteed to be up-to-date in realtime unless the value was returned by `/accounts/balance/get`.\n\nIf `current` is `null` this field is guaranteed not to be `null`.", + "format": "double", + "nullable": true, + "type": "number" + }, + "current": { + "description": "The total amount of funds in or owed by the account.\n\nFor `credit`-type accounts, a positive balance indicates the amount owed; a negative amount indicates the lender owing the account holder.\n\nFor `loan`-type accounts, the current balance is the principal remaining on the loan, except in the case of student loan accounts at Sallie Mae (`ins_116944`). For Sallie Mae student loans, the account's balance includes both principal and any outstanding interest.\n\nFor `investment`-type accounts (or `brokerage`-type accounts for API versions 2018-05-22 and earlier), the current balance is the total value of assets as presented by the institution.\n\nNote that balance information may be cached unless the value was returned by `/accounts/balance/get`; if the Item is enabled for Transactions, the balance will be at least as recent as the most recent Transaction update. If you require realtime balance information, use the `available` balance as provided by `/accounts/balance/get`.\n\nWhen returned by `/accounts/balance/get`, this field may be `null`. When this happens, `available` is guaranteed not to be `null`.", + "format": "double", + "nullable": true, + "type": "number" + }, + "iso_currency_code": { + "description": "The ISO-4217 currency code of the balance. Always null if `unofficial_currency_code` is non-null.", + "nullable": true, + "type": "string" + }, + "last_updated_datetime": { + "description": "Timestamp in [ISO 8601](https://wikipedia.org/wiki/ISO_8601) format (`YYYY-MM-DDTHH:mm:ssZ`) indicating the last time that the balance for the given account has been updated\n\nThis is currently only provided when the `min_last_updated_datetime` is passed when calling `/accounts/balance/get` for `ins_128026` (Capital One).", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "limit": { + "description": "For `credit`-type accounts, this represents the credit limit.\n\nFor `depository`-type accounts, this represents the pre-arranged overdraft limit, which is common for current (checking) accounts in Europe.\n\nIn North America, this field is typically only available for `credit`-type accounts.", + "format": "double", + "nullable": true, + "type": "number" + }, + "unofficial_currency_code": { + "description": "The unofficial currency code associated with the balance. Always null if `iso_currency_code` is non-null. Unofficial currency codes are used for currencies that do not have official ISO currency codes, such as cryptocurrencies and the currencies of certain countries.\n\nSee the [currency code schema](https://plaid.com/docs/api/accounts#currency-code-schema) for a full listing of supported `unofficial_currency_code`s.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "available", + "current", + "limit", + "iso_currency_code", + "unofficial_currency_code" + ], + "title": "AccountBalance", + "type": "object" + } + +Value: + { + "available": 43200, + "current": 43200, + "iso_currency_code": "USD", + "unofficial_currency_code": null + } + And Error at "/owners/0/addresses/0/data/country": property "country" is missing +Schema: + { + "additionalProperties": true, + "description": "Data about the components comprising an address.", + "properties": { + "city": { + "description": "The full city name", + "nullable": true, + "type": "string" + }, + "country": { + "description": "The ISO 3166-1 alpha-2 country code", + "nullable": true, + "type": "string" + }, + "postal_code": { + "description": "The postal code. In API versions 2018-05-22 and earlier, this field is called `zip`.", + "nullable": true, + "type": "string" + }, + "region": { + "description": "The region or state. In API versions 2018-05-22 and earlier, this field is called `state`.\nExample: `\"NC\"`", + "nullable": true, + "type": "string" + }, + "street": { + "description": "The full street address\nExample: `\"564 Main Street, APT 15\"`", + "type": "string" + } + }, + "required": [ + "city", + "region", + "street", + "postal_code", + "country" + ], + "title": "AddressData", + "type": "object" + } + +Value: + { + "city": "Malakoff", + "postal_code": "14236", + "region": "NY", + "street": "2992 Cameron Road" + } + | Error at "/owners/0/addresses/1/data/country": property "country" is missing +Schema: + { + "additionalProperties": true, + "description": "Data about the components comprising an address.", + "properties": { + "city": { + "description": "The full city name", + "nullable": true, + "type": "string" + }, + "country": { + "description": "The ISO 3166-1 alpha-2 country code", + "nullable": true, + "type": "string" + }, + "postal_code": { + "description": "The postal code. In API versions 2018-05-22 and earlier, this field is called `zip`.", + "nullable": true, + "type": "string" + }, + "region": { + "description": "The region or state. In API versions 2018-05-22 and earlier, this field is called `state`.\nExample: `\"NC\"`", + "nullable": true, + "type": "string" + }, + "street": { + "description": "The full street address\nExample: `\"564 Main Street, APT 15\"`", + "type": "string" + } + }, + "required": [ + "city", + "region", + "street", + "postal_code", + "country" + ], + "title": "AddressData", + "type": "object" + } + +Value: + { + "city": "San Matias", + "postal_code": "93405-2255", + "region": "CA", + "street": "2493 Leisure Lane" + } + | Error at "/report/items/0/accounts/1": doesn't match schema due to: Error at "/balances/limit": property "limit" is missing +Schema: + { + "additionalProperties": true, + "description": "A set of fields describing the balance for an account. Balance information may be cached unless the balance object was returned by `/accounts/balance/get`.", + "properties": { + "available": { + "description": "The amount of funds available to be withdrawn from the account, as determined by the financial institution.\n\nFor `credit`-type accounts, the `available` balance typically equals the `limit` less the `current` balance, less any pending outflows plus any pending inflows.\n\nFor `depository`-type accounts, the `available` balance typically equals the `current` balance less any pending outflows plus any pending inflows. For `depository`-type accounts, the `available` balance does not include the overdraft limit.\n\nFor `investment`-type accounts (or `brokerage`-type accounts for API versions 2018-05-22 and earlier), the `available` balance is the total cash available to withdraw as presented by the institution.\n\nNote that not all institutions calculate the `available` balance. In the event that `available` balance is unavailable, Plaid will return an `available` balance value of `null`.\n\nAvailable balance may be cached and is not guaranteed to be up-to-date in realtime unless the value was returned by `/accounts/balance/get`.\n\nIf `current` is `null` this field is guaranteed not to be `null`.", + "format": "double", + "nullable": true, + "type": "number" + }, + "current": { + "description": "The total amount of funds in or owed by the account.\n\nFor `credit`-type accounts, a positive balance indicates the amount owed; a negative amount indicates the lender owing the account holder.\n\nFor `loan`-type accounts, the current balance is the principal remaining on the loan, except in the case of student loan accounts at Sallie Mae (`ins_116944`). For Sallie Mae student loans, the account's balance includes both principal and any outstanding interest.\n\nFor `investment`-type accounts (or `brokerage`-type accounts for API versions 2018-05-22 and earlier), the current balance is the total value of assets as presented by the institution.\n\nNote that balance information may be cached unless the value was returned by `/accounts/balance/get`; if the Item is enabled for Transactions, the balance will be at least as recent as the most recent Transaction update. If you require realtime balance information, use the `available` balance as provided by `/accounts/balance/get`.\n\nWhen returned by `/accounts/balance/get`, this field may be `null`. When this happens, `available` is guaranteed not to be `null`.", + "format": "double", + "nullable": true, + "type": "number" + }, + "iso_currency_code": { + "description": "The ISO-4217 currency code of the balance. Always null if `unofficial_currency_code` is non-null.", + "nullable": true, + "type": "string" + }, + "last_updated_datetime": { + "description": "Timestamp in [ISO 8601](https://wikipedia.org/wiki/ISO_8601) format (`YYYY-MM-DDTHH:mm:ssZ`) indicating the last time that the balance for the given account has been updated\n\nThis is currently only provided when the `min_last_updated_datetime` is passed when calling `/accounts/balance/get` for `ins_128026` (Capital One).", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "limit": { + "description": "For `credit`-type accounts, this represents the credit limit.\n\nFor `depository`-type accounts, this represents the pre-arranged overdraft limit, which is common for current (checking) accounts in Europe.\n\nIn North America, this field is typically only available for `credit`-type accounts.", + "format": "double", + "nullable": true, + "type": "number" + }, + "unofficial_currency_code": { + "description": "The unofficial currency code associated with the balance. Always null if `iso_currency_code` is non-null. Unofficial currency codes are used for currencies that do not have official ISO currency codes, such as cryptocurrencies and the currencies of certain countries.\n\nSee the [currency code schema](https://plaid.com/docs/api/accounts#currency-code-schema) for a full listing of supported `unofficial_currency_code`s.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "available", + "current", + "limit", + "iso_currency_code", + "unofficial_currency_code" + ], + "title": "AccountBalance", + "type": "object" + } + +Value: + { + "available": 100, + "current": 110, + "iso_currency_code": "USD", + "unofficial_currency_code": null + } + And Error at "/owners/0/addresses/0/data/country": property "country" is missing +Schema: + { + "additionalProperties": true, + "description": "Data about the components comprising an address.", + "properties": { + "city": { + "description": "The full city name", + "nullable": true, + "type": "string" + }, + "country": { + "description": "The ISO 3166-1 alpha-2 country code", + "nullable": true, + "type": "string" + }, + "postal_code": { + "description": "The postal code. In API versions 2018-05-22 and earlier, this field is called `zip`.", + "nullable": true, + "type": "string" + }, + "region": { + "description": "The region or state. In API versions 2018-05-22 and earlier, this field is called `state`.\nExample: `\"NC\"`", + "nullable": true, + "type": "string" + }, + "street": { + "description": "The full street address\nExample: `\"564 Main Street, APT 15\"`", + "type": "string" + } + }, + "required": [ + "city", + "region", + "street", + "postal_code", + "country" + ], + "title": "AddressData", + "type": "object" + } + +Value: + { + "city": "Malakoff", + "postal_code": "14236", + "region": "NY", + "street": "2992 Cameron Road" + } + | Error at "/owners/0/addresses/1/data/country": property "country" is missing +Schema: + { + "additionalProperties": true, + "description": "Data about the components comprising an address.", + "properties": { + "city": { + "description": "The full city name", + "nullable": true, + "type": "string" + }, + "country": { + "description": "The ISO 3166-1 alpha-2 country code", + "nullable": true, + "type": "string" + }, + "postal_code": { + "description": "The postal code. In API versions 2018-05-22 and earlier, this field is called `zip`.", + "nullable": true, + "type": "string" + }, + "region": { + "description": "The region or state. In API versions 2018-05-22 and earlier, this field is called `state`.\nExample: `\"NC\"`", + "nullable": true, + "type": "string" + }, + "street": { + "description": "The full street address\nExample: `\"564 Main Street, APT 15\"`", + "type": "string" + } + }, + "required": [ + "city", + "region", + "street", + "postal_code", + "country" + ], + "title": "AddressData", + "type": "object" + } + +Value: + { + "city": "San Matias", + "postal_code": "93405-2255", + "region": "CA", + "street": "2493 Leisure Lane" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/pocketsmith_com_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pocketsmith_com_2_0_openapi_yaml__validate new file mode 100644 index 000000000..a930b8678 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pocketsmith_com_2_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /users/{id}/trend_analysis: invalid operation GET: invalid example: value must be an integer +Schema: + { + "type": "integer" + } + +Value: + true diff --git a/openapi3/testdata/apis_guru_openapi_directory/portfoliooptimizer_io_1_0_9_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/portfoliooptimizer_io_1_0_9_openapi_yaml__validate new file mode 100644 index 000000000..602b86a30 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/portfoliooptimizer_io_1_0_9_openapi_yaml__validate @@ -0,0 +1,17 @@ +invalid paths: invalid path /assets/correlation/matrix/denoised: invalid operation POST: invalid example: example Example of failing denoising method: Error at "/assetsCorrelationMatrix": Value is not nullable +Schema: + { + "description": "assetsCorrelationMatrix[i][j] is the correlation between the asset i and the asset j; assetsCorrelationMatrix is possibly null in case the denoising method did not manage to denoise the provided asset correlation matrix", + "items": { + "items": { + "type": "number" + }, + "minItems": 2, + "type": "array" + }, + "minItems": 2, + "type": "array" + } + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/presalytics_io_story_0_3_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/presalytics_io_story_0_3_1_openapi_yaml__validate new file mode 100644 index 000000000..fd329a649 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/presalytics_io_story_0_3_1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "session": extra sibling fields: [nullable] diff --git a/openapi3/testdata/apis_guru_openapi_directory/pressassociation_io_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/pressassociation_io_2_0_openapi_yaml__validate new file mode 100644 index 000000000..e1c4a8499 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/pressassociation_io_2_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid paths: invalid path /asset: invalid operation GET: parameter "updatedAfter" schema is invalid: invalid default: string doesn't match the regular expression "date-time" +Schema: + { + "default": "2015-05-05T00:00:00.000Z", + "pattern": "date-time", + "type": "string" + } + +Value: + "2015-05-05T00:00:00.000Z" diff --git a/openapi3/testdata/apis_guru_openapi_directory/probely_com_1_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/probely_com_1_2_0_openapi_yaml__validate new file mode 100644 index 000000000..6242d574c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/probely_com_1_2_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Account": invalid example: string doesn't match the format "date": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/quotes_rest_5_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/quotes_rest_5_1_openapi_yaml__validate new file mode 100644 index 000000000..8d08905c4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/quotes_rest_5_1_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid paths: invalid path /quote/categories/search: invalid operation GET: parameter "query" schema is invalid: invalid default: value must be a string +Schema: + { + "default": 0, + "format": "string", + "type": "string" + } + +Value: + 0 diff --git a/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_football_prediction_2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_football_prediction_2_openapi_yaml__validate new file mode 100644 index 000000000..bb8c605c9 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_football_prediction_2_openapi_yaml__validate @@ -0,0 +1,16 @@ +invalid paths: invalid path /api/v2/list-federations: invalid operation GET: invalid example: example 0: value must be an object +Schema: + { + "properties": { + "data": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + +Value: + "{\"data\": [\"UEFA\", \"OFC\", \"CAF\", \"CONCACAF\", \"CONMEBOL\", \"AFC\"]}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_idealspot_geodata_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_idealspot_geodata_1_0_openapi_yaml__validate new file mode 100644 index 000000000..89d63cf40 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rapidapi_com_idealspot_geodata_1_0_openapi_yaml__validate @@ -0,0 +1,16 @@ +invalid components: schema "Category": invalid example: Error at "/description": Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null + | Error at "/parent_id": Value is not nullable +Schema: + { + "type": "string" + } + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/rebilly_com_2_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rebilly_com_2_1_openapi_yaml__validate new file mode 100644 index 000000000..164870ca1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rebilly_com_2_1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AML": invalid example: string doesn't match the format "date": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/redeal_io_analytics_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/redeal_io_analytics_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..2b401adf7 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/redeal_io_analytics_1_0_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid components: schema "EventRecord": invalid example: value must be a string +Schema: + { + "example": 51.4353, + "type": "string" + } + +Value: + 51.4353 diff --git a/openapi3/testdata/apis_guru_openapi_directory/redhat_com_catalog_inventory_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/redhat_com_catalog_inventory_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..c9bb29b29 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/redhat_com_catalog_inventory_1_0_0_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "CheckAvailabilityTask": invalid example: value must be an object +Schema: + { + "example": "Task payload input content", + "readOnly": true, + "title": "Content", + "type": "object" + } + +Value: + "Task payload input content" diff --git a/openapi3/testdata/apis_guru_openapi_directory/remove_bg_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/remove_bg_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..9a151e7d0 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/remove_bg_1_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "RemoveBgJson": invalid default: value must be a boolean +Schema: + { + "default": "false", + "description": "Whether to add an artificial shadow to the result (default: false). NOTE: Adding shadows is currently only supported for car photos. Other subjects are returned without shadow, even if set to true (this might change in the future).\n", + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/rentcast_io_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rentcast_io_1_0_openapi_yaml__validate new file mode 100644 index 000000000..fe6355c60 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rentcast_io_1_0_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid paths: invalid path /avm/rent/long-term: invalid operation GET: invalid example: example Response: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/rev_ai_v1_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/rev_ai_v1_openapi_yaml__load new file mode 100644 index 000000000..a774cef9f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rev_ai_v1_openapi_yaml__load @@ -0,0 +1 @@ +bad data in "#/components/responses/CaptionSrt" (expecting ref to schema object) diff --git a/openapi3/testdata/apis_guru_openapi_directory/reverb_com_3_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/reverb_com_3_0_openapi_yaml__validate new file mode 100644 index 000000000..d166048d8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/reverb_com_3_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/conversations/{id}/offer" and "/conversations/{conversation_id}/offer" diff --git a/openapi3/testdata/apis_guru_openapi_directory/ritekit_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ritekit_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..57aa8f434 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ritekit_com_1_0_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /v1/images/quote: invalid operation GET: invalid example: value must be an integer +Schema: + { + "type": "integer" + } + +Value: + "60" diff --git a/openapi3/testdata/apis_guru_openapi_directory/rubrikinc_github_io_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rubrikinc_github_io_v1_openapi_yaml__validate new file mode 100644 index 000000000..3e2518d94 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rubrikinc_github_io_v1_openapi_yaml__validate @@ -0,0 +1,15 @@ +invalid paths: invalid path /certificate: invalid operation GET: parameter "sort_by" schema is invalid: invalid default: value is not one of the allowed values ["name","description","hasKey","expiration"] +Schema: + { + "default": "Name", + "enum": [ + "name", + "description", + "hasKey", + "expiration" + ], + "type": "string" + } + +Value: + "Name" diff --git a/openapi3/testdata/apis_guru_openapi_directory/rudder_example_local_17_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/rudder_example_local_17_openapi_yaml__validate new file mode 100644 index 000000000..010f655fc --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rudder_example_local_17_openapi_yaml__validate @@ -0,0 +1,19 @@ +invalid components: schema "api-endpoints": invalid example: value must be an object +Schema: + { + "description": "objects with two fields, the first one has the endpoint name as key and its description as value, the second one has HTTP verb to use (GET, POST PUT, DELETE) as key and the supported version an API path for value.", + "example": "{ 'listAcceptedNodes': 'List all accepted nodes with configurable details level', 'GET': '[8,9,10,11,12,13] /nodes' }", + "properties": { + "endpointName": { + "description": "The endpoint name for key and its description for value", + "type": "string" + }, + "httpVerb": { + "format": "The HTTP verb for the endpoint for key and the supported version and API path for value" + } + }, + "type": "object" + } + +Value: + "{ 'listAcceptedNodes': 'List all accepted nodes with configurable details level', 'GET': '[8,9,10,11,12,13] /nodes' }" diff --git a/openapi3/testdata/apis_guru_openapi_directory/rumble_run_2_15_0_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/rumble_run_2_15_0_openapi_yaml__load new file mode 100644 index 000000000..f6b8f587e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/rumble_run_2_15_0_openapi_yaml__load @@ -0,0 +1 @@ +bad data in "#/components/responses/StatusMessage" (expecting ref to schema object) diff --git a/openapi3/testdata/apis_guru_openapi_directory/sakari_io_1_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/sakari_io_1_0_1_openapi_yaml__validate new file mode 100644 index 000000000..36489638e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/sakari_io_1_0_1_openapi_yaml__validate @@ -0,0 +1,14 @@ +invalid components: schema "AccountEvent": invalid example: value is not one of the allowed values ["account","messageStatus","messageIncoming"] +Schema: + { + "enum": [ + "account", + "messageStatus", + "messageIncoming" + ], + "example": "message", + "type": "string" + } + +Value: + "message" diff --git a/openapi3/testdata/apis_guru_openapi_directory/salesforce_local_einstein_2_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/salesforce_local_einstein_2_0_1_openapi_yaml__validate new file mode 100644 index 000000000..316faa740 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/salesforce_local_einstein_2_0_1_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "LearningCurve": invalid example: value must be an object +Schema: + { + "description": "Epoch to which the metrics correspond.", + "example": 1, + "type": "object" + } + +Value: + 1 diff --git a/openapi3/testdata/apis_guru_openapi_directory/salesloft_com_v2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/salesloft_com_v2_openapi_yaml__validate new file mode 100644 index 000000000..473f15ed2 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/salesloft_com_v2_openapi_yaml__validate @@ -0,0 +1,14 @@ +invalid components: schema "ActivityHistory": invalid example: value must be an object +Schema: + { + "description": "A list of remote resource names that failed to load. This is specific to the type of activity and may change over time. Not returned for create requests", + "example": [ + "email" + ], + "type": "object" + } + +Value: + [ + "email" + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/scideas_net_regression_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/scideas_net_regression_1_0_openapi_yaml__validate new file mode 100644 index 000000000..f87a13c74 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/scideas_net_regression_1_0_openapi_yaml__validate @@ -0,0 +1,20 @@ +invalid components: schema "inline_response_200": invalid example: Error at "/0": value must be an object +Schema: + { + "description": "array of text summaries, one per contributing varible", + "format": "string", + "type": "object" + } + +Value: + "state has a negligible (0) influence." + | Error at "/1": value must be an object +Schema: + { + "description": "array of text summaries, one per contributing varible", + "format": "string", + "type": "object" + } + +Value: + "discount has a small (2) influence. More discount makes sales higher" diff --git a/openapi3/testdata/apis_guru_openapi_directory/seldon_local_engine_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/seldon_local_engine_0_1_openapi_yaml__validate new file mode 100644 index 000000000..16de1b9e5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/seldon_local_engine_0_1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "DefaultData": error parsing regexp: invalid repeat count: `{1,2097152}` diff --git a/openapi3/testdata/apis_guru_openapi_directory/seldon_local_wrapper_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/seldon_local_wrapper_0_1_openapi_yaml__validate new file mode 100644 index 000000000..131255467 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/seldon_local_wrapper_0_1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "DefaultData": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/sendgrid_com_1_0_0_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/sendgrid_com_1_0_0_openapi_yaml__load new file mode 100644 index 000000000..e5fb18fb8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/sendgrid_com_1_0_0_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error converting YAML to JSON: yaml: control characters are not allowed diff --git a/openapi3/testdata/apis_guru_openapi_directory/shipengine_com_1_1_202304191404_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/shipengine_com_1_1_202304191404_openapi_yaml__validate new file mode 100644 index 000000000..c022b9a1e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/shipengine_com_1_1_202304191404_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "address_validating_shipment": invalid example: value must be a string +Schema: + { + "description": "The National Motor Freight Traffic Association [freight class](http://www.nmfta.org/pages/nmfc?AspxAutoDetectCookieSupport=1), such as \"77.5\", \"110\", or \"250\".\n", + "example": 77.5, + "nullable": true, + "type": "string" + } + +Value: + 77.5 diff --git a/openapi3/testdata/apis_guru_openapi_directory/shop_pro_jp_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/shop_pro_jp_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..c67e5ed96 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/shop_pro_jp_1_0_0_openapi_yaml__validate @@ -0,0 +1,15 @@ +invalid components: schema "InlineScriptTag": invalid example: value is not one of the allowed values ["all","thanks_page","cart"] +Schema: + { + "description": "インラインスクリプトを出力するページ。\n\n- `all`: カートの途中のページと注文完了ページの両方\n- `thanks_page`: 注文完了ページ\n- `cart`: カートの途中のページ\n", + "enum": [ + "all", + "thanks_page", + "cart" + ], + "example": "shop", + "type": "string" + } + +Value: + "shop" diff --git a/openapi3/testdata/apis_guru_openapi_directory/shotstack_io_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/shotstack_io_v1_openapi_yaml__validate new file mode 100644 index 000000000..e408f2ae4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/shotstack_io_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AssetRenderResponse": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/shutterstock_com_1_1_32_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/shutterstock_com_1_1_32_openapi_yaml__validate new file mode 100644 index 000000000..98d64af2d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/shutterstock_com_1_1_32_openapi_yaml__validate @@ -0,0 +1,27 @@ +invalid components: schema "AudioUrl": invalid example: Error at "/url": property "url" is missing +Schema: + { + "description": "Audio License URL object", + "example": { + "$ref": "#/components/schemas/Url/example" + }, + "properties": { + "shorts_loops_stems": { + "description": "URL that can be used to download the .zip file containing shorts, loops, and stems", + "type": "string" + }, + "url": { + "description": "URL that can be used to download the unwatermarked, licensed asset", + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + +Value: + { + "$ref": "#/components/schemas/Url/example" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/signl4_com_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/signl4_com_v1_openapi_yaml__validate new file mode 100644 index 000000000..1ff6110c8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/signl4_com_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/teams/{teamId}/schedules/{scheduleId}" and "/teams/{teamId}/schedules/{dutyId}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/sinao_app_1_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/sinao_app_1_1_0_openapi_yaml__validate new file mode 100644 index 000000000..fe2ae0e6f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/sinao_app_1_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Invoice": extra sibling fields: [type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/slack_com_1_7_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/slack_com_1_7_0_openapi_yaml__validate new file mode 100644 index 000000000..405cd9cbd --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/slack_com_1_7_0_openapi_yaml__validate @@ -0,0 +1,18 @@ +invalid paths: invalid path /admin.conversations.archive: invalid operation POST: invalid example: example response: Error at "/error": value is not one of the allowed values ["feature_not_enabled","channel_not_found","channel_type_not_supported","default_org_wide_channel","already_archived","cant_archive_general","restricted_action","could_not_archive_channel"] +Schema: + { + "enum": [ + "feature_not_enabled", + "channel_not_found", + "channel_type_not_supported", + "default_org_wide_channel", + "already_archived", + "cant_archive_general", + "restricted_action", + "could_not_archive_channel" + ], + "type": "string" + } + +Value: + "invalid_auth" diff --git a/openapi3/testdata/apis_guru_openapi_directory/sms77_io_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/sms77_io_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..ed571195e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/sms77_io_1_0_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /contacts: invalid operation POST: invalid example: value must be a string +Schema: + { + "example": 152, + "type": "string" + } + +Value: + 152 diff --git a/openapi3/testdata/apis_guru_openapi_directory/snyk_io_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/snyk_io_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..8ba66eb30 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/snyk_io_1_0_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /group/{groupId}/audit: invalid operation POST: invalid example: value must be a number +Schema: + { + "type": "number" + } + +Value: + "1" diff --git a/openapi3/testdata/apis_guru_openapi_directory/soundcloud_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/soundcloud_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..414146caa --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/soundcloud_com_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Activities": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/spinitron_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/spinitron_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..e2a9c0f22 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/spinitron_com_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Playlist": invalid example: string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/spoonacular_com_1_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/spoonacular_com_1_1_openapi_yaml__validate new file mode 100644 index 000000000..007ff1bfa --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/spoonacular_com_1_1_openapi_yaml__validate @@ -0,0 +1,36 @@ +invalid paths: invalid path /food/products/upc/{upc}: invalid operation GET: invalid example: example example-1: Error at "/ingredients/0/description": Value is not nullable +Schema: + {} + +Value: + null + | Error at "/ingredients/0/safety_level": Value is not nullable +Schema: + {} + +Value: + null + | Error at "/ingredients/1/description": Value is not nullable +Schema: + {} + +Value: + null + | Error at "/ingredients/1/safety_level": Value is not nullable +Schema: + {} + +Value: + null + | Error at "/ingredients/2/description": Value is not nullable +Schema: + {} + +Value: + null + | Error at "/ingredients/2/safety_level": Value is not nullable +Schema: + {} + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/sportsdata_io_mlb_v3_rotoballer_articles_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/sportsdata_io_mlb_v3_rotoballer_articles_1_0_openapi_yaml__validate new file mode 100644 index 000000000..3a5ac5356 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/sportsdata_io_mlb_v3_rotoballer_articles_1_0_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid paths: invalid path /{format}/RotoBallerArticles: invalid operation GET: invalid example: example response: value must be an array +Schema: + { + "items": { + "$ref": "#/components/schemas/Article" + }, + "type": "array" + } + +Value: + "[\r\n {\r\n \"ArticleID\": 0,\r\n \"Title\": \"string\",\r\n \"Source\": \"string\",\r\n \"Updated\": \"string\",\r\n \"Content\": \"string\",\r\n \"Url\": \"string\",\r\n \"TermsOfUse\": \"string\",\r\n \"Author\": \"string\",\r\n \"Players\": [\r\n {\r\n \"PlayerID\": 0,\r\n \"Name\": \"string\",\r\n \"TeamID\": 0,\r\n \"Team\": \"string\",\r\n \"Position\": \"string\"\r\n }\r\n ]\r\n }\r\n]" diff --git a/openapi3/testdata/apis_guru_openapi_directory/spotify_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/spotify_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..130b71c4a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/spotify_com_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AlbumObject": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/squareup_com_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/squareup_com_2_0_openapi_yaml__validate new file mode 100644 index 000000000..ac67f7960 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/squareup_com_2_0_openapi_yaml__validate @@ -0,0 +1,147 @@ +invalid components: schema "AccumulateLoyaltyPointsRequest": invalid example: Error at "/accumulate_points": property "accumulate_points" is missing +Schema: + { + "description": "A request to accumulate points for a purchase.", + "example": { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + }, + "properties": { + "accumulate_points": { + "$ref": "#/components/schemas/LoyaltyEventAccumulatePoints" + }, + "idempotency_key": { + "description": "A unique string that identifies the `AccumulateLoyaltyPoints` request. \nKeys can be any valid string but must be unique for every request.", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "location_id": { + "description": "The [location](https://developer.squareup.com/reference/square_2021-08-18/objects/Location) where the purchase was made.", + "type": "string" + } + }, + "required": [ + "accumulate_points", + "idempotency_key", + "location_id" + ], + "type": "object", + "x-release-status": "PUBLIC" + } + +Value: + { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + } + | Error at "/idempotency_key": property "idempotency_key" is missing +Schema: + { + "description": "A request to accumulate points for a purchase.", + "example": { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + }, + "properties": { + "accumulate_points": { + "$ref": "#/components/schemas/LoyaltyEventAccumulatePoints" + }, + "idempotency_key": { + "description": "A unique string that identifies the `AccumulateLoyaltyPoints` request. \nKeys can be any valid string but must be unique for every request.", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "location_id": { + "description": "The [location](https://developer.squareup.com/reference/square_2021-08-18/objects/Location) where the purchase was made.", + "type": "string" + } + }, + "required": [ + "accumulate_points", + "idempotency_key", + "location_id" + ], + "type": "object", + "x-release-status": "PUBLIC" + } + +Value: + { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + } + | Error at "/location_id": property "location_id" is missing +Schema: + { + "description": "A request to accumulate points for a purchase.", + "example": { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + }, + "properties": { + "accumulate_points": { + "$ref": "#/components/schemas/LoyaltyEventAccumulatePoints" + }, + "idempotency_key": { + "description": "A unique string that identifies the `AccumulateLoyaltyPoints` request. \nKeys can be any valid string but must be unique for every request.", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "location_id": { + "description": "The [location](https://developer.squareup.com/reference/square_2021-08-18/objects/Location) where the purchase was made.", + "type": "string" + } + }, + "required": [ + "accumulate_points", + "idempotency_key", + "location_id" + ], + "type": "object", + "x-release-status": "PUBLIC" + } + +Value: + { + "request_body": { + "accumulate_points": { + "order_id": "RFZfrdtm3mhO1oGzf5Cx7fEMsmGZY" + }, + "idempotency_key": "58b90739-c3e8-4b11-85f7-e636d48d72cb", + "location_id": "P034NEENMD09F" + }, + "request_params": "?account_id=5adcb100-07f1-4ee7-b8c6-6bb9ebc474bd" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/staging_ecotaco_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/staging_ecotaco_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..b2504d795 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/staging_ecotaco_com_1_0_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /catchement_areas/{id}: invalid operation GET: invalid example: value must be a number +Schema: + { + "type": "number" + } + +Value: + "1" diff --git a/openapi3/testdata/apis_guru_openapi_directory/statsocial_com_1_0_0_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/statsocial_com_1_0_0_openapi_yaml__load new file mode 100644 index 000000000..099ffe7fb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/statsocial_com_1_0_0_openapi_yaml__load @@ -0,0 +1 @@ +map key "18_24" not found diff --git a/openapi3/testdata/apis_guru_openapi_directory/stellastra_com_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/stellastra_com_1_0_openapi_yaml__validate new file mode 100644 index 000000000..5c604c4ec --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/stellastra_com_1_0_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid paths: invalid path /post-review: invalid operation POST: invalid example: value must be a string +Schema: + { + "description": "User's email must exist and must not use a professional domain (I.E., not from a free service).", + "type": "string" + } + +Value: + { + "user_email": "johnsmith@companyxyz.com" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/stoplight_io_api_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/stoplight_io_api_v1_openapi_yaml__validate new file mode 100644 index 000000000..1fbd1c245 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/stoplight_io_api_v1_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid paths: invalid path /versions/{versionId}/import: invalid operation PUT: invalid default: value must be a boolean +Schema: + { + "default": "", + "description": "Default: false.", + "type": "boolean" + } + +Value: + "" diff --git a/openapi3/testdata/apis_guru_openapi_directory/stream_io_api_com_v80_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/stream_io_api_com_v80_2_0_openapi_yaml__validate new file mode 100644 index 000000000..ffc17efe1 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/stream_io_api_com_v80_2_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "App": extra sibling fields: [description title] diff --git a/openapi3/testdata/apis_guru_openapi_directory/superset_apache_local_superset_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/superset_apache_local_superset_v1_openapi_yaml__validate new file mode 100644 index 000000000..1e9f42728 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/superset_apache_local_superset_v1_openapi_yaml__validate @@ -0,0 +1,21 @@ +invalid components: schema "ChartRestApi.post": invalid example: value must be a string +Schema: + { + "description": "The type of chart visualization used.", + "example": [ + "bar", + "line_multi", + "area", + "table" + ], + "maxLength": 250, + "type": "string" + } + +Value: + [ + "bar", + "line_multi", + "area", + "table" + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/surevoip_co_uk_9dcb0dc8_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/surevoip_co_uk_9dcb0dc8_openapi_yaml__validate new file mode 100644 index 000000000..d297df4a8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/surevoip_co_uk_9dcb0dc8_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid paths: invalid path /calls: invalid operation POST: invalid example: value must be a string +Schema: + { + "description": "is in seconds. Just take 120 secs away from the hangup_at time for a announcement to be played 2 mins before the end of the call. If provided announcement_id is required\n", + "example": 180, + "type": "string" + } + +Value: + 180 diff --git a/openapi3/testdata/apis_guru_openapi_directory/svix_com_1_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/svix_com_1_4_openapi_yaml__validate new file mode 100644 index 000000000..944cc3b93 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/svix_com_1_4_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "EndpointDisabledEvent": invalid example: Error at "/data/failSince": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/swaggerhub_com_1_0_66_swagger_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/swaggerhub_com_1_0_66_swagger_yaml__validate new file mode 100644 index 000000000..bbf33eb98 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/swaggerhub_com_1_0_66_swagger_yaml__validate @@ -0,0 +1 @@ +value of openapi must be a non-empty string diff --git a/openapi3/testdata/apis_guru_openapi_directory/tafqit_herokuapp_com_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/tafqit_herokuapp_com_v1_openapi_yaml__validate new file mode 100644 index 000000000..37987ff21 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/tafqit_herokuapp_com_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: invalid path /convert: invalid operation POST: invalid external docs: url is required diff --git a/openapi3/testdata/apis_guru_openapi_directory/telnyx_com_2_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/telnyx_com_2_0_0_openapi_yaml__validate new file mode 100644 index 000000000..78b05cc87 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/telnyx_com_2_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "BillingGroup": invalid example: Error at "/deleted_at": Value is not nullable +Schema: + { + "description": "ISO 8601 formatted date indicating when the resource was removed.", + "format": "date-time", + "type": "string" + } + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/telstra_com_3_x_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/telstra_com_3_x_openapi_yaml__validate new file mode 100644 index 000000000..d4de504fb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/telstra_com_3_x_openapi_yaml__validate @@ -0,0 +1,12 @@ +invalid components: schema "Virtual-number": invalid example: value must be a string +Schema: + { + "description": "The Virtual Number assigned to your account.\n", + "example": 401234567, + "maxLength": 10, + "minLength": 10, + "type": "string" + } + +Value: + 401234567 diff --git a/openapi3/testdata/apis_guru_openapi_directory/thebluealliance_com_3_8_2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/thebluealliance_com_3_8_2_openapi_yaml__validate new file mode 100644 index 000000000..bc7befed4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/thebluealliance_com_3_8_2_openapi_yaml__validate @@ -0,0 +1,18 @@ +invalid components: schema "Zebra": invalid example: value must be a number +Schema: + { + "example": [ + 2.73, + 2.7, + null + ], + "format": "double", + "type": "number" + } + +Value: + [ + 2.73, + 2.7, + null + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_discovery_v2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_discovery_v2_openapi_yaml__validate new file mode 100644 index 000000000..0afc0fd77 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_discovery_v2_openapi_yaml__validate @@ -0,0 +1,12 @@ +invalid components: schema "AccessDates": invalid example: value must be a boolean +Schema: + { + "default": false, + "description": "Boolean flag to indicate whether or not the access end date is approximated", + "example": "yyyy-MM-ddThh-mm-ssZ", + "type": "boolean", + "x-position": 3 + } + +Value: + "yyyy-MM-ddThh-mm-ssZ" diff --git a/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_publish_v2_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_publish_v2_openapi_yaml__validate new file mode 100644 index 000000000..0afc0fd77 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/ticketmaster_com_publish_v2_openapi_yaml__validate @@ -0,0 +1,12 @@ +invalid components: schema "AccessDates": invalid example: value must be a boolean +Schema: + { + "default": false, + "description": "Boolean flag to indicate whether or not the access end date is approximated", + "example": "yyyy-MM-ddThh-mm-ssZ", + "type": "boolean", + "x-position": 3 + } + +Value: + "yyyy-MM-ddThh-mm-ssZ" diff --git a/openapi3/testdata/apis_guru_openapi_directory/tomtom_com_routing_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/tomtom_com_routing_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..55eeb50e5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/tomtom_com_routing_1_0_0_openapi_yaml__validate @@ -0,0 +1,44 @@ +invalid components: schema "calculateReachableRangePostDataParameters": invalid example: Error at "/avoidAreas/rectangles/0/northEastCorner/latitude": value must be a string +Schema: + { + "type": "string", + "xml": { + "attribute": true + } + } + +Value: + 48.90309 + | Error at "/avoidAreas/rectangles/0/northEastCorner/longitude": value must be a string +Schema: + { + "type": "string", + "xml": { + "attribute": true + } + } + +Value: + 2.41115 + | Error at "/avoidAreas/rectangles/0/southWestCorner/latitude": value must be a string +Schema: + { + "type": "string", + "xml": { + "attribute": true + } + } + +Value: + 48.81851 + | Error at "/avoidAreas/rectangles/0/southWestCorner/longitude": value must be a string +Schema: + { + "type": "string", + "xml": { + "attribute": true + } + } + +Value: + 2.26593 diff --git a/openapi3/testdata/apis_guru_openapi_directory/trakt_tv_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/trakt_tv_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..d1b0c43f4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/trakt_tv_1_0_0_openapi_yaml__validate @@ -0,0 +1,80 @@ +invalid components: request body "Remove_items_from_collectionBody": invalid example: Error at "/shows/0/seasons": property "seasons" is missing +Schema: + { + "properties": { + "ids": { + "properties": { + "imdb": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "tmdb": { + "type": "number" + }, + "trakt": { + "type": "number" + }, + "tvdb": { + "type": "number" + } + }, + "type": "object" + }, + "seasons": { + "items": { + "properties": { + "episodes": { + "items": { + "properties": { + "number": { + "type": "number" + } + }, + "required": [ + "number" + ], + "type": "object" + }, + "type": "array" + }, + "number": { + "type": "number" + } + }, + "required": [ + "number" + ], + "type": "object" + }, + "type": "array" + }, + "title": { + "type": "string" + }, + "year": { + "type": "number" + } + }, + "required": [ + "title", + "year", + "ids", + "seasons" + ], + "type": "object" + } + +Value: + { + "ids": { + "imdb": "tt0903747", + "slug": "breaking-bad", + "tmdb": 1396, + "trakt": 1, + "tvdb": 81189 + }, + "title": "Breaking Bad", + "year": 2008 + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/trashnothing_com_1_3_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/trashnothing_com_1_3_openapi_yaml__validate new file mode 100644 index 000000000..9ce7a298e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/trashnothing_com_1_3_openapi_yaml__validate @@ -0,0 +1,18 @@ +invalid components: schema "Feedback": invalid example: Error at "/date": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/reviewer_user_id": value must be a string +Schema: + { + "description": "The user ID of the user that submitted the feedback.", + "type": "string" + } + +Value: + 9191 + | Error at "/user_id": value must be a string +Schema: + { + "description": "The user ID of the user that the feedback is about.", + "type": "string" + } + +Value: + 2946512 diff --git a/openapi3/testdata/apis_guru_openapi_directory/trello_com_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/trello_com_1_0_openapi_yaml__validate new file mode 100644 index 000000000..f3c84ac9a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/trello_com_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/boards/{idBoard}/cards/{idCard}" and "/boards/{idBoard}/cards/{filter}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/truora_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/truora_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..ca292b00a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/truora_com_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "APIKeyVersion": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_api_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_api_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..17fb02232 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_api_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "api.v2010.account": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_autopilot_v1_1_53_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_autopilot_v1_1_53_0_openapi_yaml__validate new file mode 100644 index 000000000..497a613cb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_autopilot_v1_1_53_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "autopilot.v1.assistant.model_build": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..f08cd990d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "chat.v1.credential": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..46ce884d6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "chat.v2.credential": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v3_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v3_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..35c98d882 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_chat_v3_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "chat.v3.channel": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_conversations_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_conversations_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..ad35f51ec --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_conversations_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "conversations.v1.configuration.configuration_webhook": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_events_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_events_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..e1152be67 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_events_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "events.v1.sink": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_flex_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_flex_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..356c9c87c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_flex_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "flex.v1.configuration": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_frontline_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_frontline_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..6b305e1cb --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_frontline_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "frontline.v1.user": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_insights_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_insights_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..e4fc413da --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_insights_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "insights.v1.call.annotation": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_intelligence_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_intelligence_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..5fead52d8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_intelligence_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "intelligence.v2.service": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..f6bedea05 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "ip_messaging.v1.credential": extra sibling fields: [nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..b055b097e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_ip_messaging_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "ip_messaging.v2.credential": extra sibling fields: [nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_lookups_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_lookups_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..7b3399cfd --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_lookups_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "lookups.v2.phone_number": extra sibling fields: [type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_media_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_media_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..8dee60941 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_media_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "media.v1.media_processor": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_messaging_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_messaging_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..1a6eb9582 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_messaging_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "messaging.v1.brand_registrations": extra sibling fields: [type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_notify_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_notify_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..37e09f0f3 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_notify_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "notify.v1.credential": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..44cf72c3c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "numbers.v1.porting_bulk_portability": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..f1770a357 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_numbers_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "numbers.v2.authorization_document": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_preview_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_preview_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..2ce46d782 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_preview_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "preview.hosted_numbers.authorization_document": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_proxy_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_proxy_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..62974f162 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_proxy_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "proxy.v1.service": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_serverless_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_serverless_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..41da1e2f2 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_serverless_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "serverless.v1.service.asset.asset_version": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..6bca67071 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "studio.v1.flow": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..407dbd26f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_studio_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "studio.v2.flow": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_supersim_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_supersim_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..aa7bba669 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_supersim_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "supersim.v1.esim_profile": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_sync_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_sync_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..5899cf8bc --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_sync_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: invalid path /v1/Services/{ServiceSid}/Lists/{ListSid}/Items: invalid operation GET: parameter "Order" schema is invalid: extra sibling fields: [type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_taskrouter_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_taskrouter_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..6327f0486 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_taskrouter_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "taskrouter.v1.workspace": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trunking_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trunking_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..0e09c8379 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trunking_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "trunking.v1.trunk": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trusthub_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trusthub_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..add2d9ecc --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_trusthub_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "trusthub.v1.customer_profile": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_verify_v2_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_verify_v2_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..a543f8be7 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_verify_v2_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "verify.v2.form": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_video_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_video_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..75e974386 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_video_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "video.v1.composition": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_wireless_v1_1_55_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_wireless_v1_1_55_0_openapi_yaml__validate new file mode 100644 index 000000000..8fa39e061 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twilio_com_twilio_wireless_v1_1_55_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "wireless.v1.command": extra sibling fields: [description nullable type] diff --git a/openapi3/testdata/apis_guru_openapi_directory/twinehealth_com_v7_78_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twinehealth_com_v7_78_1_openapi_yaml__validate new file mode 100644 index 000000000..59bf42ee6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twinehealth_com_v7_78_1_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid components: schema "CalendarEventResource": invalid example: Error at "/attributes/completed_by": value must be an object +Schema: + { + "description": "The coach who marked the calendar event as completed. Only valid for `plan-check-in` event type.", + "type": "object" + } + +Value: + "5a0c8e27a9d454cc150997c9" diff --git a/openapi3/testdata/apis_guru_openapi_directory/twitter_com_current_2_62_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/twitter_com_current_2_62_openapi_yaml__validate new file mode 100644 index 000000000..0a83aee3a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/twitter_com_current_2_62_openapi_yaml__validate @@ -0,0 +1,267 @@ +invalid components: schema "Expansions": invalid example: Error at "/created_at": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/edit_history_tweet_ids": property "edit_history_tweet_ids" is missing +Schema: + { + "example": { + "author_id": "2244994945", + "created_at": "Wed Jan 06 18:40:40 +0000 2021", + "id": "1346889436626259968", + "text": "Learn how to use the user Tweet timeline and user mention timeline endpoints in the Twitter API v2 to explore Tweet\\u2026 https:\\/\\/t.co\\/56a0vZUx7i" + }, + "properties": { + "attachments": { + "description": "Specifies the type of attachments (if any) present in this Tweet.", + "properties": { + "media_keys": { + "description": "A list of Media Keys for each one of the media attachments (if media are attached).", + "items": { + "$ref": "#/components/schemas/MediaKey" + }, + "minItems": 1, + "type": "array" + }, + "poll_ids": { + "description": "A list of poll IDs (if polls are attached).", + "items": { + "$ref": "#/components/schemas/PollId" + }, + "minItems": 1, + "type": "array" + } + }, + "type": "object" + }, + "author_id": { + "$ref": "#/components/schemas/UserId" + }, + "context_annotations": { + "items": { + "$ref": "#/components/schemas/ContextAnnotation" + }, + "minItems": 1, + "type": "array" + }, + "conversation_id": { + "$ref": "#/components/schemas/TweetId" + }, + "created_at": { + "description": "Creation time of the Tweet.", + "example": "2021-01-06T18:40:40.000Z", + "format": "date-time", + "type": "string" + }, + "edit_controls": { + "properties": { + "editable_until": { + "description": "Time when Tweet is no longer editable.", + "example": "2021-01-06T18:40:40.000Z", + "format": "date-time", + "type": "string" + }, + "edits_remaining": { + "description": "Number of times this Tweet can be edited.", + "type": "integer" + }, + "is_edit_eligible": { + "description": "Indicates if this Tweet is eligible to be edited.", + "example": false, + "type": "boolean" + } + }, + "required": [ + "is_edit_eligible", + "editable_until", + "edits_remaining" + ], + "type": "object" + }, + "edit_history_tweet_ids": { + "description": "A list of Tweet Ids in this Tweet chain.", + "items": { + "$ref": "#/components/schemas/TweetId" + }, + "minItems": 1, + "type": "array" + }, + "entities": { + "$ref": "#/components/schemas/FullTextEntities" + }, + "geo": { + "description": "The location tagged on the Tweet, if the user provided one.", + "properties": { + "coordinates": { + "$ref": "#/components/schemas/Point" + }, + "place_id": { + "$ref": "#/components/schemas/PlaceId" + } + }, + "type": "object" + }, + "id": { + "$ref": "#/components/schemas/TweetId" + }, + "in_reply_to_user_id": { + "$ref": "#/components/schemas/UserId" + }, + "lang": { + "description": "Language of the Tweet, if detected by Twitter. Returned as a BCP47 language tag.", + "example": "en", + "type": "string" + }, + "non_public_metrics": { + "description": "Nonpublic engagement metrics for the Tweet at the time of the request.", + "properties": { + "impression_count": { + "description": "Number of times this Tweet has been viewed.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "organic_metrics": { + "description": "Organic nonpublic engagement metrics for the Tweet at the time of the request.", + "properties": { + "impression_count": { + "description": "Number of times this Tweet has been viewed.", + "type": "integer" + }, + "like_count": { + "description": "Number of times this Tweet has been liked.", + "type": "integer" + }, + "reply_count": { + "description": "Number of times this Tweet has been replied to.", + "type": "integer" + }, + "retweet_count": { + "description": "Number of times this Tweet has been Retweeted.", + "type": "integer" + } + }, + "required": [ + "impression_count", + "retweet_count", + "reply_count", + "like_count" + ], + "type": "object" + }, + "possibly_sensitive": { + "description": "Indicates if this Tweet contains URLs marked as sensitive, for example content suitable for mature audiences.", + "example": false, + "type": "boolean" + }, + "promoted_metrics": { + "description": "Promoted nonpublic engagement metrics for the Tweet at the time of the request.", + "properties": { + "impression_count": { + "description": "Number of times this Tweet has been viewed.", + "format": "int32", + "type": "integer" + }, + "like_count": { + "description": "Number of times this Tweet has been liked.", + "format": "int32", + "type": "integer" + }, + "reply_count": { + "description": "Number of times this Tweet has been replied to.", + "format": "int32", + "type": "integer" + }, + "retweet_count": { + "description": "Number of times this Tweet has been Retweeted.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "public_metrics": { + "description": "Engagement metrics for the Tweet at the time of the request.", + "properties": { + "impression_count": { + "description": "Number of times this Tweet has been viewed.", + "format": "int32", + "type": "integer" + }, + "like_count": { + "description": "Number of times this Tweet has been liked.", + "type": "integer" + }, + "quote_count": { + "description": "Number of times this Tweet has been quoted.", + "type": "integer" + }, + "reply_count": { + "description": "Number of times this Tweet has been replied to.", + "type": "integer" + }, + "retweet_count": { + "description": "Number of times this Tweet has been Retweeted.", + "type": "integer" + } + }, + "required": [ + "retweet_count", + "reply_count", + "like_count", + "impression_count" + ], + "type": "object" + }, + "referenced_tweets": { + "description": "A list of Tweets this Tweet refers to. For example, if the parent Tweet is a Retweet, a Quoted Tweet or a Reply, it will include the related Tweet referenced to by its parent.", + "items": { + "properties": { + "id": { + "$ref": "#/components/schemas/TweetId" + }, + "type": { + "enum": [ + "retweeted", + "quoted", + "replied_to" + ], + "type": "string" + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "minItems": 1, + "type": "array" + }, + "reply_settings": { + "$ref": "#/components/schemas/ReplySettings" + }, + "source": { + "description": "This is deprecated.", + "type": "string" + }, + "text": { + "$ref": "#/components/schemas/TweetText" + }, + "withheld": { + "$ref": "#/components/schemas/TweetWithheld" + } + }, + "required": [ + "id", + "text", + "edit_history_tweet_ids" + ], + "type": "object" + } + +Value: + { + "author_id": "2244994945", + "created_at": "Wed Jan 06 18:40:40 +0000 2021", + "id": "1346889436626259968", + "text": "Learn how to use the user Tweet timeline and user mention timeline endpoints in the Twitter API v2 to explore Tweet\\u2026 https:\\/\\/t.co\\/56a0vZUx7i" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/unicourt_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/unicourt_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..0dc66a53a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/unicourt_com_1_0_0_openapi_yaml__validate @@ -0,0 +1,13 @@ +invalid components: schema "Case": invalid example: minimum string length is 18 +Schema: + { + "description": "Document ID which is the parent document for the current document. This will be null if the current document is a parent document.", + "example": "CDOC3Ygn4ooAvNjHv", + "maxLength": 18, + "minLength": 18, + "nullable": true, + "type": "string" + } + +Value: + "CDOC3Ygn4ooAvNjHv" diff --git a/openapi3/testdata/apis_guru_openapi_directory/va_gov_benefits_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/va_gov_benefits_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..46db592e8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/va_gov_benefits_1_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "DocumentUploadStatus": invalid example: value must be an integer +Schema: + { + "description": "The document height", + "example": "11.0", + "type": "integer" + } + +Value: + "11.0" diff --git a/openapi3/testdata/apis_guru_openapi_directory/va_gov_facilities_0_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/va_gov_facilities_0_0_1_openapi_yaml__validate new file mode 100644 index 000000000..f5e409529 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/va_gov_facilities_0_0_1_openapi_yaml__validate @@ -0,0 +1,26 @@ +invalid components: schema "Distance": invalid example: value must be an object +Schema: + { + "description": "Distance to facility in miles using decimal format. Used when querying for facilities proximal to a location. ", + "example": 54.13, + "properties": { + "distance": { + "description": "Distance to facility in decimal format.", + "example": 54.13, + "type": "number" + }, + "id": { + "description": "Identifier of facility.", + "example": "vc_0101V", + "type": "string" + } + }, + "required": [ + "distance", + "id" + ], + "type": "object" + } + +Value: + 54.13 diff --git a/openapi3/testdata/apis_guru_openapi_directory/va_gov_forms_0_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/va_gov_forms_0_0_0_openapi_yaml__validate new file mode 100644 index 000000000..f7ab00f5d --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/va_gov_forms_0_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "FormShow": invalid example: value must be a boolean +Schema: + { + "description": "A flag indicating whether the form url was confirmed as a valid download", + "example": "true", + "type": "boolean" + } + +Value: + "true" diff --git a/openapi3/testdata/apis_guru_openapi_directory/velopayments_com_2_35_57_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/velopayments_com_2_35_57_openapi_yaml__validate new file mode 100644 index 000000000..de5c926e0 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/velopayments_com_2_35_57_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "CreateFundingAccountRequestV2": invalid example: Error at "/routingNumber": maximum string length is 9 +Schema: + { + "description": "Required if type is either FBO or PRIVATE", + "maxLength": 9, + "minLength": 6, + "type": "string" + } + +Value: + "routingNumber" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vercel_com_0_0_1_openapi_yaml__load b/openapi3/testdata/apis_guru_openapi_directory/vercel_com_0_0_1_openapi_yaml__load new file mode 100644 index 000000000..2a6522f53 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vercel_com_0_0_1_openapi_yaml__load @@ -0,0 +1 @@ +failed to unmarshal data: json error: invalid character 'o' looking for beginning of value, yaml error: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal bool into field Schema.properties of type openapi3.Schema diff --git a/openapi3/testdata/apis_guru_openapi_directory/viator_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/viator_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..d6d4c760b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/viator_com_1_0_0_openapi_yaml__validate @@ -0,0 +1,225 @@ +invalid paths: invalid path /available/products: invalid operation POST: invalid example: example 1: doesn't match schema due to: Error at "/errorMessage": value must be an array +Schema: + { + "description": "**array** of error message strings", + "items": {}, + "nullable": true, + "type": "array" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/errorMessageText": value must be a string +Schema: + { + "description": "**array** of error message strings in plain text", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/errorName": value must be a string +Schema: + { + "description": "**name** of *this* type of error", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/errorReference": value must be a string +Schema: + { + "description": "**reference number** of *this* error", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/errorType": value must be a string +Schema: + { + "description": "**code** specifying the type of error", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + And Error at "/data/0/admission": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/essential": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/onRequestPeriod": value must be an integer +Schema: + { + "description": "**number** of hours before the travel date that *this* product will be 'on-request' for\n- this field will contain a value if the `bookingEngineId` is `'FreesaleOnRequestBE'`\n- an `onRequestPeriod` of 48 hours means that *this* product is freesale up until 48 hours before the travel date, and is on-request for 48 hours or less until the travel date\n- **note**: 'hours in advance' (the number of hours a product is available for booking before the travel date) may also affect this; however, this value is not available in the API\n", + "nullable": true, + "type": "integer" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/primaryGroupId": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/savingAmount": value must be a string +Schema: + { + "description": "Ignore (Viator only)\n", + "type": "string" + } + +Value: + 0 + | Error at "/data/0/specialReservationDetails": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/uniqueShortDescription": value must be a string +Schema: + { + "description": "**natural-language description** of *this* product", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/0/webURL": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/admission": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/essential": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/primaryGroupId": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/savingAmount": value must be a string +Schema: + { + "description": "Ignore (Viator only)\n", + "type": "string" + } + +Value: + 0 + | Error at "/data/1/specialReservationDetails": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/uniqueShortDescription": value must be a string +Schema: + { + "description": "**natural-language description** of *this* product", + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } + | Error at "/data/1/webURL": value must be a string +Schema: + { + "description": "ignore (Viator only)", + "nullable": true, + "type": "string" + } + +Value: + { + "$ref": "#/components/examples/product-example-1/value/data/pas" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/vimeo_com_3_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vimeo_com_3_4_openapi_yaml__validate new file mode 100644 index 000000000..00d1efe17 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vimeo_com_3_4_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "activity-3-1": invalid example: value must be a boolean +Schema: + { + "description": "Whether this picture is the active picture for its parent resource.", + "example": "true", + "type": "boolean" + } + +Value: + "true" diff --git a/openapi3/testdata/apis_guru_openapi_directory/visma_com_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/visma_com_1_0_openapi_yaml__validate new file mode 100644 index 000000000..554571b78 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/visma_com_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "ActivityActivityType": extra sibling fields: [readOnly] diff --git a/openapi3/testdata/apis_guru_openapi_directory/visma_net_9_66_02_1023_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/visma_net_9_66_02_1023_openapi_yaml__validate new file mode 100644 index 000000000..c40b9d5a5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/visma_net_9_66_02_1023_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/controller/api/v1/inventory/internal/{inventoryId}" and "/controller/api/v1/inventory/internal/{inventoryID}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/visualcrossing_com_weather_4_6_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/visualcrossing_com_weather_4_6_openapi_yaml__validate new file mode 100644 index 000000000..f814141f6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/visualcrossing_com_weather_4_6_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid paths: invalid path /VisualCrossingWebServices/rest/services/weatherdata/forecast: invalid operation GET: invalid example: value must be a boolean +Schema: + { + "type": "boolean" + } + +Value: + "false" diff --git a/openapi3/testdata/apis_guru_openapi_directory/visualstudio_com_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/visualstudio_com_v1_openapi_yaml__validate new file mode 100644 index 000000000..544e7034e --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/visualstudio_com_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "ScopedCreateSecretBody": error parsing regexp: invalid or unsupported Perl syntax: `(?!` diff --git a/openapi3/testdata/apis_guru_openapi_directory/vonage_com_reports_1_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_reports_1_0_1_openapi_yaml__validate new file mode 100644 index 000000000..492766a51 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_reports_1_0_1_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "CallLog": invalid example: value must be a string +Schema: + { + "description": "Source number of the call", + "example": 17325550100, + "type": "string" + } + +Value: + 17325550100 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vonage_com_user_1_11_8_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_user_1_11_8_openapi_yaml__validate new file mode 100644 index 000000000..a43323c02 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_user_1_11_8_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid components: parameter "AccountID": invalid example: value must be a string +Schema: + { + "type": "string" + } + +Value: + 451496 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vonage_com_vgis_1_0_1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_vgis_1_0_1_openapi_yaml__validate new file mode 100644 index 000000000..55c7d4b8c --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vonage_com_vgis_1_0_1_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "User": invalid example: value must be a string +Schema: + { + "description": "Contact number of the user", + "example": 14155550100, + "type": "string" + } + +Value: + 14155550100 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..83eb9c663 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/api/catalog/pvt/subcollection/{subCollectionId}/brand/{categoryId}" and "/api/catalog/pvt/subcollection/{subCollectionId}/brand/{brandId}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_Seller_Portal_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_Seller_Portal_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..e98368b50 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Catalog_API_Seller_Portal_1_0_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/api/catalog-seller-portal/products/{productId}" and "/api/catalog-seller-portal/products/{param}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Checkout_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Checkout_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..bf6d441f5 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Checkout_API_1_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /api/checkout/pub/orderForm/{orderFormId}/coupons: invalid operation POST: invalid example: example response: Error at "/shippingData/logisticsInfo/0/slas/0/deliveryIds/0/warehouseId": value must be a string +Schema: + { + "description": "Warehouse ID.", + "type": "string" + } + +Value: + 11 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Customer_Credit_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Customer_Credit_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..d57c88ea8 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Customer_Credit_API_1_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid paths: invalid path /api/creditcontrol/accounts/{accountId}: invalid operation PUT: invalid default: value must be an integer +Schema: + { + "default": "100.0", + "description": "If the user don't set a credit limit, the system will define 100 for default", + "type": "integer" + } + +Value: + "100.0" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_GiftCard_Hub_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_GiftCard_Hub_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..d8eec1849 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_GiftCard_Hub_API_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: conflicting paths "/giftcardproviders/{giftCardProviderId}" and "/giftcardproviders/{giftCardProviderID}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_License_Manager_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_License_Manager_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..b5896bc72 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_License_Manager_API_1_0_openapi_yaml__validate @@ -0,0 +1 @@ +invalid paths: invalid path /api/vlm/account: invalid operation GET: invalid example: Error at "/appKeys/0/createdIn": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/appKeys/1/createdIn": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/creationDate": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Logistics_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Logistics_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..08ec413a6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Logistics_API_1_0_openapi_yaml__validate @@ -0,0 +1,8 @@ +invalid components: schema "CreateReservation": invalid example: Error at "/dockId": value must be a string +Schema: + { + "type": "string" + } + +Value: + 111 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Marketplace_Protocol_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Marketplace_Protocol_1_0_openapi_yaml__validate new file mode 100644 index 000000000..40adbc080 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Marketplace_Protocol_1_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid paths: invalid path /api/checkout/pub/orderForms/simulation: invalid operation POST: invalid example: Error at "/logisticsInfo/0/slas/0/deliveryIds/0/warehouseId": value must be a string +Schema: + { + "description": "Warehouse ID.", + "type": "string" + } + +Value: + 11 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..41725b9d4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API_1_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid components: schema "DeliveryId": invalid example: Error at "/warehouseId": value must be a string +Schema: + { + "description": "ID of the [warehouse](https://help.vtex.com/tutorial/warehouse--6oIxvsVDTtGpO7y6zwhGpb).", + "type": "string" + } + +Value: + 11 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API__PII_version__1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API__PII_version__1_0_openapi_yaml__validate new file mode 100644 index 000000000..c811d26df --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Orders_API__PII_version__1_0_openapi_yaml__validate @@ -0,0 +1,9 @@ +invalid components: schema "DeliveryId": invalid example: Error at "/warehouseId": value must be a string +Schema: + { + "description": "Warehouse ID.", + "type": "string" + } + +Value: + 11 diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Policies_System_API_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Policies_System_API_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..760167265 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Policies_System_API_1_0_0_openapi_yaml__validate @@ -0,0 +1,124 @@ +invalid paths: invalid path /api/policy-engine/policies/{id}: invalid operation POST: invalid example: Error at "/0/statements/0/effect": property "effect" is missing +Schema: + { + "properties": { + "actions": { + "description": "Actions that the Policy will execute", + "items": {}, + "properties": { + "id": { + "description": "Action ID. The possible values can be `SendSlackMessage`, `SendEmail`, and `DeactivatePromotions`", + "title": "id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "description": "Data inside of the actions", + "title": "metadata", + "type": "object" + } + }, + "title": "actions", + "type": "array" + }, + "condition": { + "description": "Condition to activate this policy. This object can have a maximum of ten recursive conditions", + "properties": { + "conditions": { + "description": "List of conditions that will activate the policy", + "items": { + "properties": { + "conditions": { + "description": "These are the conditions the actions can have. The possible values are `[]`, `stringEquals`, and `numericGreaterThan`", + "items": { + "type": "string" + }, + "title": "conditions", + "type": "array" + }, + "key": { + "description": "The element that will define what the policy will influence. This field has the possible values `skuId`, `brandId`, `discountPercentage`", + "title": "key", + "type": "string" + }, + "operation": { + "description": "The action of the condition. This operation possible values are `None`, `stringEquals`, `stringEqualsIgnoreCase`, `numericEquals`, `numericLessThan`, `numericLessThanEquals`, `numericGreaterThan`, `numericGreaterThanEquals`, `bool`, `not`, `or`, `and`, `dateTimeUtcGreaterThan`, `dateTimeUtcLessThan`, and `between`", + "title": "operation", + "type": "string" + }, + "values": { + "description": "Value of the key", + "items": { + "type": "string" + }, + "title": "values", + "type": "array" + } + } + }, + "type": "array" + } + }, + "title": "condition", + "type": "object" + }, + "effect": { + "default": "Allow", + "description": "This field is not functional at the moment. To create a correct request, fill the field with `Allow`", + "title": "effect", + "type": "string" + }, + "operation": { + "description": "This operation will determine if all the conditions need to be valid or at least one of them, if the conditions array is not empty. The possible values to these fields are `None`, `stringEquals`, `stringEqualsIgnoreCase`, `numericEquals`, `numericLessThan`, `numericLessThanEquals`, `numericGreaterThan`, `numericGreaterThanEquals`, `bool`, `not`, `or`, `and`, `dateTimeUtcGreaterThan`, `dateTimeUtcLessThan`, and `between`", + "title": "operation", + "type": "string" + }, + "resource": { + "description": "Scope on which this policy must be evaluated", + "title": "resource", + "type": "string" + } + }, + "required": [ + "effect" + ], + "type": "object" + } + +Value: + { + "actions": [ + { + "id": "SendSlackMessage", + "metadata": { + "alertDescription": "Avoid selling products from Berenice with a discount greater than 70%.", + "channel": "C01NJFF35R6", + "relatedUsers": [ + "URUNDC2NB" + ] + } + } + ], + "condition": { + "conditions": [ + { + "conditions": [], + "key": "brandId", + "operation": "stringEquals", + "values": [ + "2000001" + ] + }, + { + "conditions": [], + "key": "discountPercentage", + "operation": "numericGreaterThan", + "values": [ + "70.00" + ] + } + ], + "operation": "and" + }, + "resource": "vrn:vtex.promotions-alert:aws-us-east-1:kamila:master:/_v/promotions_alert" + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Price_Simulations_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Price_Simulations_1_0_openapi_yaml__validate new file mode 100644 index 000000000..ec3da5672 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Price_Simulations_1_0_openapi_yaml__validate @@ -0,0 +1,37 @@ +invalid paths: invalid path /_v/custom-prices/rules: invalid operation POST: invalid default: Error at "/pricetable": property "pricetable" is missing +Schema: + { + "default": {}, + "properties": { + "email": { + "default": "", + "description": "User's email", + "title": "email" + }, + "orderType": { + "default": "", + "description": "Order type", + "title": "orderType", + "type": "string" + }, + "pricetable": { + "default": "", + "description": "Name of the Price Table associated with the scenario", + "title": "pricetable", + "type": "string" + }, + "state": { + "default": "", + "description": "Delivery location", + "title": "state", + "type": "string" + } + }, + "required": [ + "pricetable" + ], + "type": "object" + } + +Value: + {} diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_SKU_Bindings_API_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_SKU_Bindings_API_1_0_openapi_yaml__validate new file mode 100644 index 000000000..9cf908e4b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_SKU_Bindings_API_1_0_openapi_yaml__validate @@ -0,0 +1,63 @@ +invalid paths: invalid path /catalog/pvt/skusellers/{skuId}: invalid operation GET: invalid example: value must be an object +Schema: + { + "description": "Object with information about an SKU Binding.", + "example": [ + { + "Id": 48, + "IsActive": true, + "LastUpdateDate": "2020-10-21T19:13:00.657", + "SalesPolicy": 0, + "SellerId": "cosmetics1", + "SellerSkuId": "42", + "StockKeepingUnitId": 1 + } + ], + "properties": { + "Id": { + "description": "SKU Binding ID.", + "format": "int32", + "type": "integer" + }, + "IsActive": { + "description": "Defines if the SKU binding is active.", + "type": "boolean" + }, + "LastUpdateDate": { + "description": "Date when the SKU binding was updated for the last time, in UTC format.", + "type": "string" + }, + "SalesPolicy": { + "description": "Sales policy ID.", + "format": "int32", + "type": "integer" + }, + "SellerId": { + "description": "ID that identifies the seller in the marketplace. It can be the same as the seller name or a unique number. Check the **Sellers management** section in the Admin to get the correct ID.", + "type": "string" + }, + "SellerSkuId": { + "description": "SKU seller ID.", + "type": "string" + }, + "StockKeepingUnitId": { + "description": "SKU ID in the VTEX marketplace.", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + +Value: + [ + { + "Id": 48, + "IsActive": true, + "LastUpdateDate": "2020-10-21T19:13:00.657", + "SalesPolicy": 0, + "SellerId": "cosmetics1", + "SellerSkuId": "42", + "StockKeepingUnitId": 1 + } + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v2__1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v2__1_0_openapi_yaml__validate new file mode 100644 index 000000000..8353ea5f0 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v2__1_0_openapi_yaml__validate @@ -0,0 +1,18 @@ +invalid components: schema "settings": invalid example: value must be an array +Schema: + { + "default": [], + "description": "Array containing delivery channels.", + "example": "delivery", + "items": { + "default": "", + "description": "Type of delivery channel. The values that are possible are: `pickup-in-point` for pickup point and `delivery` for regular delivery.", + "example": "delivery", + "type": "string" + }, + "title": "deliveryChannels", + "type": "array" + } + +Value: + "delivery" diff --git a/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v3__1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v3__1_0_openapi_yaml__validate new file mode 100644 index 000000000..ff35ddc68 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/vtex_local_Subscriptions_API__v3__1_0_openapi_yaml__validate @@ -0,0 +1,17 @@ +invalid components: schema "settings": invalid example: value must be an array +Schema: + { + "default": [], + "description": "Array containing delivery channels.", + "example": "delivery", + "items": { + "description": "Type of delivery channel. The values that are possible are: `pickupInPoint` for pickup point and `delivery` for regular delivery.", + "example": "delivery", + "type": "string" + }, + "title": "deliveryChannels", + "type": "array" + } + +Value: + "delivery" diff --git a/openapi3/testdata/apis_guru_openapi_directory/walletobjects_googleapis_com_pay_passes_v1_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/walletobjects_googleapis_com_pay_passes_v1_openapi_yaml__validate new file mode 100644 index 000000000..af8c5fbb0 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/walletobjects_googleapis_com_pay_passes_v1_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "AddMessageRequest": extra sibling fields: [description] diff --git a/openapi3/testdata/apis_guru_openapi_directory/walmart_com_inventory_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/walmart_com_inventory_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..5939121bd --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/walmart_com_inventory_1_0_0_openapi_yaml__validate @@ -0,0 +1,67 @@ +invalid paths: invalid path /v3/feeds: invalid operation POST: invalid example: example MultiNode_Bulk_Inventory_Update_Request: Error at "/file": property "file" is missing +Schema: + { + "properties": { + "file": { + "description": "Feed file to upload", + "format": "binary", + "type": "string" + } + }, + "required": [ + "file" + ], + "type": "object" + } + +Value: + { + "inventory": [ + { + "shipNodes": [ + { + "quantity": { + "amount": 100, + "unit": "EACH" + }, + "shipNode": "10000003527" + } + ], + "sku": "190086893939" + }, + { + "shipNodes": [ + { + "quantity": { + "amount": 21, + "unit": "EACH" + }, + "shipNode": "10000003527" + } + ], + "sku": "685387364107_SS4_01" + }, + { + "shipNodes": [ + { + "quantity": { + "amount": 13, + "unit": "EACH" + }, + "shipNode": "10000003527" + }, + { + "quantity": { + "amount": 12, + "unit": "EACH" + }, + "shipNode": "1000000352" + } + ], + "sku": "5700521553133_toolsnonwfs_2" + } + ], + "inventoryHeader": { + "version": "1.5" + } + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/walmart_com_price_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/walmart_com_price_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..f85c1846a --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/walmart_com_price_1_0_0_openapi_yaml__validate @@ -0,0 +1,58 @@ +invalid paths: invalid path /v3/feeds: invalid operation POST: invalid example: example json1: Error at "/file": property "file" is missing +Schema: + { + "properties": { + "file": { + "description": "Feed file to upload", + "format": "binary", + "type": "string" + } + }, + "required": [ + "file" + ], + "type": "object" + } + +Value: + { + "Price": [ + { + "pricing": [ + { + "comparisonPrice": { + "amount": 4.99, + "currency": "USD" + }, + "comparisonPriceType": "BASE", + "currentPrice": { + "amount": 3, + "currency": "USD" + }, + "currentPriceType": "REDUCED" + } + ], + "sku": "30348_KFTest" + }, + { + "pricing": [ + { + "comparisonPrice": { + "amount": 4.99, + "currency": "USD" + }, + "comparisonPriceType": "BASE", + "currentPrice": { + "amount": 3, + "currency": "USD" + }, + "currentPriceType": "REDUCED" + } + ], + "sku": "OT-PP7F-QGUG" + } + ], + "PriceHeader": { + "version": "1.7" + } + } diff --git a/openapi3/testdata/apis_guru_openapi_directory/wealthreader_com_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/wealthreader_com_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..38ec15062 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/wealthreader_com_1_0_0_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "entities": invalid example: value must be a boolean +Schema: + { + "description": "Indica si el campo es requerido", + "example": 0, + "type": "boolean" + } + +Value: + 0 diff --git a/openapi3/testdata/apis_guru_openapi_directory/webscraping_ai_3_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/webscraping_ai_3_0_0_openapi_yaml__validate new file mode 100644 index 000000000..fe5f441f4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/webscraping_ai_3_0_0_openapi_yaml__validate @@ -0,0 +1,6 @@ +invalid components: parameter "headers": invalid example: validation failed due to: at '': got string, want object +Schema: + null + +Value: + null diff --git a/openapi3/testdata/apis_guru_openapi_directory/whapi_com_bets_2_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/whapi_com_bets_2_0_0_openapi_yaml__validate new file mode 100644 index 000000000..558b581a6 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/whapi_com_bets_2_0_0_openapi_yaml__validate @@ -0,0 +1,12 @@ +invalid paths: invalid path /bet/complex: invalid operation POST: invalid example: example response: value must be an array +Schema: + { + "items": { + "$ref": "#/components/schemas/betPlaced" + }, + "title": "placedBets", + "type": "array" + } + +Value: + "{\n \"placedBets\" : [\n {\n \"id\" : 51097,\n \"number\": 1,\n \"receipt\" : \"O/0013333/0000109/F\", \n \"numLines\" : 1, \n \"totalStake\" : 2.50, \n \"placedDateTime\" : \"2015-12-12T00:11:00\"\n }, \n {\n \"id\" : 51098, \n \"number\": 2,\n \"receipt\" : \"O/0013333/0000109/F\", \n \"numLines\" : 1, \n \"totalStake\" : 2.50, \n \"placedDateTime\" : \"2015-12-12T00:11:00\"\n }\n ]\n}\n" diff --git a/openapi3/testdata/apis_guru_openapi_directory/whatsapp_local_1_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/whatsapp_local_1_0_openapi_yaml__validate new file mode 100644 index 000000000..ffe7bf76b --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/whatsapp_local_1_0_openapi_yaml__validate @@ -0,0 +1,15 @@ +invalid components: schema "Application-Settings": invalid example: Error at "/auto_download/0": value is not one of the allowed values ["audio","document","voice","video","image."] +Schema: + { + "enum": [ + "audio", + "document", + "voice", + "video", + "image." + ], + "type": "string" + } + +Value: + "image" diff --git a/openapi3/testdata/apis_guru_openapi_directory/windows_net_graphrbac_1_6_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/windows_net_graphrbac_1_6_openapi_yaml__validate new file mode 100644 index 000000000..ef875fe2f --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/windows_net_graphrbac_1_6_openapi_yaml__validate @@ -0,0 +1,17 @@ +invalid components: schema "OAuth2PermissionGrant": invalid example: Error at "/consentType": value is not one of the allowed values ["AllPrincipals","Principal"] +Schema: + { + "description": "Indicates if consent was provided by the administrator (on behalf of the organization) or by an individual.", + "enum": [ + "AllPrincipals", + "Principal" + ], + "type": "string", + "x-ms-enum": { + "modelAsString": true, + "name": "ConsentType" + } + } + +Value: + "consentType" diff --git a/openapi3/testdata/apis_guru_openapi_directory/wiremock_org_admin_2_35_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/wiremock_org_admin_2_35_0_openapi_yaml__validate new file mode 100644 index 000000000..3e91e2736 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/wiremock_org_admin_2_35_0_openapi_yaml__validate @@ -0,0 +1,34 @@ +invalid components: request body "snapshotRecording": invalid example: value must be an object +Schema: + { + "description": "Criteria for extracting response bodies to a separate file instead of including it in the stub mapping", + "example": [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ], + "properties": { + "binarySizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + }, + "textSizeThreshold": { + "default": "0", + "description": "Size threshold for extracting binary response bodies. Supports humanized size strings, e.g. \"56 Mb\". Default unit is bytes.", + "example": "18.2 GB", + "type": "string" + } + }, + "type": "object" + } + +Value: + [ + { + "binarySizeThreshold": "1 Mb", + "textSizeThreshold": "2 kb" + } + ] diff --git a/openapi3/testdata/apis_guru_openapi_directory/wyjyt_geo_calculate_azurewebsites_net_1_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/wyjyt_geo_calculate_azurewebsites_net_1_0_0_openapi_yaml__validate new file mode 100644 index 000000000..0c9c2e817 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/wyjyt_geo_calculate_azurewebsites_net_1_0_0_openapi_yaml__validate @@ -0,0 +1,23 @@ +invalid components: schema "geoConvertRequest": invalid example: value must be an object +Schema: + { + "example": "{\"coordinates\":\"39.801769 -86.157769\",\"system\":\"latlon\",\"format\":\"seconds\"}", + "properties": { + "coordinates": { + "description": "Comma-separated list of Geo Coordinates to convert. See accepted Systems below.", + "type": "string" + }, + "format": { + "description": "Output format:\r\n(decimal, degree, minutes, seconds.)\r\nDefault is decimal.", + "type": "string" + }, + "system": { + "description": "Output standard coordinate system:\r\n(latlon | utm | mgrs | ecef | epsg3857 | georef | cartesian). \r\nDefault is latlon.", + "type": "string" + } + }, + "type": "object" + } + +Value: + "{\"coordinates\":\"39.801769 -86.157769\",\"system\":\"latlon\",\"format\":\"seconds\"}" diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_accounting_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_accounting_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..aa64f6948 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_accounting_2_9_4_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "Account": invalid example: value must be a string +Schema: + { + "description": "Customer defined alpha numeric account code e.g 200 or SALES (max length = 10)", + "example": 4400, + "type": "string" + } + +Value: + 4400 diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_assets_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_assets_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..57ee46ef4 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_assets_2_9_4_openapi_yaml__validate @@ -0,0 +1 @@ +invalid components: schema "Asset": invalid example: string doesn't match the format "date": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_bankfeeds_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_bankfeeds_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..6f47d3d45 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_bankfeeds_2_9_4_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid components: schema "EndBalance": invalid example: value must be a number +Schema: + { + "example": "10.1340", + "format": "double", + "type": "number", + "x-is-money": true + } + +Value: + "10.1340" diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_files_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_files_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..a4a6136be --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_files_2_9_4_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid paths: invalid path /Associations/{ObjectId}: invalid operation GET: invalid example: value must be an array +Schema: + { + "items": { + "$ref": "#/components/schemas/Association" + }, + "type": "array" + } + +Value: + "[ { \"FileId\":\"6beccb4a-0d7d-4518-93f3-e0cd1dccb254\", \"ObjectId\":\"1270bf7c-5d18-473a-9231-1e36c4bd33ed\", \"ObjectType\":\"Business\", \"ObjectGroup\":\"Contact\" } ]" diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_identity_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_identity_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..67c9b5987 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_identity_2_9_4_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid paths: invalid path /Connections: invalid operation GET: invalid example: value must be an array +Schema: + { + "items": { + "$ref": "#/components/schemas/Connection" + }, + "type": "array" + } + +Value: + "[ { \"id\": \"7cb59f93-2964-421d-bb5e-a0f7a4572a44\", \"tenantId\": \"fe79f7dd-b6d4-4a92-ba7b-538af6289c58\", \"tenantName\": \"Demo Company (NZ)\", \"tenantType\": \"ORGANISATION\", \"createdDateUtc\": \"2019-12-07T18:46:19.5165400\", \"updatedDateUtc\": \"2019-12-07T18:46:19.5187840\" } ]" diff --git a/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_payroll_au_2_9_4_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_payroll_au_2_9_4_openapi_yaml__validate new file mode 100644 index 000000000..6bb859825 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xero_com_xero_payroll_au_2_9_4_openapi_yaml__validate @@ -0,0 +1,10 @@ +invalid components: schema "Account": invalid example: value must be a string +Schema: + { + "description": "Customer defined account code", + "example": 420, + "type": "string" + } + +Value: + 420 diff --git a/openapi3/testdata/apis_guru_openapi_directory/xtrf_eu_2_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/xtrf_eu_2_0_openapi_yaml__validate new file mode 100644 index 000000000..2cdb81891 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/xtrf_eu_2_0_openapi_yaml__validate @@ -0,0 +1,11 @@ +invalid paths: invalid path /accounting/customers/invoices: invalid operation GET: invalid example: example ref: value must be an array +Schema: + { + "items": { + "$ref": "#/components/schemas/CustomerInvoiceDTO" + }, + "type": "array" + } + +Value: + "/home-api/assets/examples/accounting/customers/invoices/getAll.json#responseBody" diff --git a/openapi3/testdata/apis_guru_openapi_directory/zoom_us_2_0_0_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/zoom_us_2_0_0_openapi_yaml__validate new file mode 100644 index 000000000..9662cd931 --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/zoom_us_2_0_0_openapi_yaml__validate @@ -0,0 +1,49 @@ +invalid paths: invalid path /accounts: invalid operation GET: invalid example: example response: doesn't match schema due to: Error at "/page_count": value must be an integer +Schema: + { + "description": "The number of pages returned for the request made.", + "type": "integer" + } + +Value: + "integer" + | Error at "/page_number": value must be an integer +Schema: + { + "default": 1, + "description": "**Deprecated**: This field has been deprecated. Please use the \"next_page_token\" field for pagination instead of this field.\n\nThe page number of the current results.", + "type": "integer" + } + +Value: + "integer" + | Error at "/page_size": value must be an integer +Schema: + { + "default": 30, + "description": "The number of records returned with a single API call.", + "maximum": 300, + "type": "integer" + } + +Value: + "integer" + | Error at "/total_records": value must be an integer +Schema: + { + "description": "The total number of all the records available across pages.", + "type": "integer" + } + +Value: + "integer" + And Error at "/accounts/0/created_at": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/accounts/0/seats": value must be an integer +Schema: + { + "description": "Account seats.", + "type": "integer" + } + +Value: + "integer" + | Error at "/accounts/0/subscription_end_time": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" | Error at "/accounts/0/subscription_start_time": string doesn't match the format "date-time": string doesn't match pattern "^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})$" diff --git a/openapi3/testdata/apis_guru_openapi_directory/zuora_com_2021_08_20_openapi_yaml__validate b/openapi3/testdata/apis_guru_openapi_directory/zuora_com_2021_08_20_openapi_yaml__validate new file mode 100644 index 000000000..0fef903bc --- /dev/null +++ b/openapi3/testdata/apis_guru_openapi_directory/zuora_com_2021_08_20_openapi_yaml__validate @@ -0,0 +1,197 @@ +invalid components: schema "CreditMemoFromChargeType": invalid example: doesn't match schema due to: Error at "/charges/0": doesn't match schema due to: Error at "/amount": Value is not nullable +Schema: + { + "description": "The amount of the credit memo item.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `224.0` or later.\n", + "format": "double", + "type": "number" + } + +Value: + null + | Error at "/productRatePlanChargeId": property "productRatePlanChargeId" is missing +Schema: + { + "properties": { + "amount": { + "description": "The amount of the credit memo item.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `224.0` or later.\n", + "format": "double", + "type": "number" + }, + "chargeId": { + "description": "The ID of the product rate plan charge that the credit memo is created from.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `257.0` or later.\n", + "type": "string" + }, + "comment": { + "description": "Comments about the product rate plan charge.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `257.0` or later.\n", + "maxLength": 255, + "type": "string" + }, + "description": { + "description": "The description of the product rate plan charge.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `257.0` or later.\n", + "maxLength": 255, + "type": "string" + }, + "financeInformation": { + "description": "Container for the finance information related to the product rate plan charge associated with the credit memo.\n", + "properties": { + "deferredRevenueAccountingCode": { + "description": "The accounting code for the deferred revenue, such as Monthly Recurring Liability.\n", + "maxLength": 100, + "type": "string" + }, + "onAccountAccountingCode": { + "description": "The accounting code that maps to an on account in your accounting system.\n", + "maxLength": 100, + "type": "string" + }, + "recognizedRevenueAccountingCode": { + "description": "The accounting code for the recognized revenue, such as Monthly Recurring Charges or Overage Charges.\n", + "maxLength": 100, + "type": "string" + }, + "revenueRecognitionRuleName": { + "description": "The name of the revenue recognition rule governing the revenue schedule.\n", + "maxLength": 100, + "type": "string" + } + }, + "type": "object" + }, + "memoItemAmount": { + "description": "The amount of the credit memo item.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `224.0` or later.\n", + "format": "double", + "type": "number" + }, + "productRatePlanChargeId": { + "description": "The ID of the product rate plan charge that the credit memo is created from.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `257.0` or later.\n", + "type": "string" + }, + "quantity": { + "description": "The number of units for the credit memo item.\n", + "format": "double", + "type": "number" + }, + "serviceEndDate": { + "description": "The service end date of the credit memo item. If not specified, the effective end date of the corresponding product rate plan will be used.\n", + "format": "date", + "type": "string" + }, + "serviceStartDate": { + "description": "The service start date of the credit memo item. If not specified, the effective start date of the corresponding product rate plan will be used.\n", + "format": "date", + "type": "string" + } + }, + "required": [ + "chargeId", + "productRatePlanChargeId" + ], + "type": "object" + } + +Value: + { + "amount": null, + "chargeId": "402890555a87d7f5015a88c613c5001e", + "comment": "this is comment1", + "quantity": 1, + "serviceEndDate": "2018-10-17", + "serviceStartDate": "2017-10-17" + } + And Error at "/amount": Value is not nullable +Schema: + { + "description": "Custom fields of the Credit Memo Item object. The name of each custom field has the form \u003ccode\u003e*customField*__c\u003c/code\u003e. Custom field names are case sensitive. See [Manage Custom Fields](https://knowledgecenter.zuora.com/BB_Introducing_Z_Business/Manage_Custom_Fields) for more information.\n" + } + +Value: + null + | Error at "/charges/1": doesn't match schema due to: Error at "/productRatePlanChargeId": property "productRatePlanChargeId" is missing +Schema: + { + "properties": { + "amount": { + "description": "The amount of the credit memo item.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `224.0` or later.\n", + "format": "double", + "type": "number" + }, + "chargeId": { + "description": "The ID of the product rate plan charge that the credit memo is created from.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `257.0` or later.\n", + "type": "string" + }, + "comment": { + "description": "Comments about the product rate plan charge.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `257.0` or later.\n", + "maxLength": 255, + "type": "string" + }, + "description": { + "description": "The description of the product rate plan charge.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `257.0` or later.\n", + "maxLength": 255, + "type": "string" + }, + "financeInformation": { + "description": "Container for the finance information related to the product rate plan charge associated with the credit memo.\n", + "properties": { + "deferredRevenueAccountingCode": { + "description": "The accounting code for the deferred revenue, such as Monthly Recurring Liability.\n", + "maxLength": 100, + "type": "string" + }, + "onAccountAccountingCode": { + "description": "The accounting code that maps to an on account in your accounting system.\n", + "maxLength": 100, + "type": "string" + }, + "recognizedRevenueAccountingCode": { + "description": "The accounting code for the recognized revenue, such as Monthly Recurring Charges or Overage Charges.\n", + "maxLength": 100, + "type": "string" + }, + "revenueRecognitionRuleName": { + "description": "The name of the revenue recognition rule governing the revenue schedule.\n", + "maxLength": 100, + "type": "string" + } + }, + "type": "object" + }, + "memoItemAmount": { + "description": "The amount of the credit memo item.\n\n**Note**: This field is not available if you set the `zuora-version` request header to `224.0` or later.\n", + "format": "double", + "type": "number" + }, + "productRatePlanChargeId": { + "description": "The ID of the product rate plan charge that the credit memo is created from.\n\n**Note**: This field is only available if you set the `zuora-version` request header to `257.0` or later.\n", + "type": "string" + }, + "quantity": { + "description": "The number of units for the credit memo item.\n", + "format": "double", + "type": "number" + }, + "serviceEndDate": { + "description": "The service end date of the credit memo item. If not specified, the effective end date of the corresponding product rate plan will be used.\n", + "format": "date", + "type": "string" + }, + "serviceStartDate": { + "description": "The service start date of the credit memo item. If not specified, the effective start date of the corresponding product rate plan will be used.\n", + "format": "date", + "type": "string" + } + }, + "required": [ + "chargeId", + "productRatePlanChargeId" + ], + "type": "object" + } + +Value: + { + "amount": 20, + "chargeId": "402890555a7d4022015a7d90906b0067", + "comment": "this is comment2", + "serviceEndDate": "2018-10-17", + "serviceStartDate": "2017-10-17" + } diff --git a/openapi3/testdata/origin/mapping_fields.yaml b/openapi3/testdata/origin/mapping_fields.yaml new file mode 100644 index 000000000..0cdb2d7d8 --- /dev/null +++ b/openapi3/testdata/origin/mapping_fields.yaml @@ -0,0 +1,27 @@ +openapi: "3.1.0" +info: + title: Mapping Fields Origin Test + version: "1.0" +paths: + /test: + get: + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + metadata: + type: object + dependentRequired: + name: + - age + - email + dependentSchemas: + credit_card: + type: object + patternProperties: + "^x-": + type: string diff --git a/openapi3/testdata/schema31_conditional.yml b/openapi3/testdata/schema31_conditional.yml new file mode 100644 index 000000000..1da51a54c --- /dev/null +++ b/openapi3/testdata/schema31_conditional.yml @@ -0,0 +1,32 @@ +openapi: "3.1.0" +info: + title: Test conditional keywords + version: "1.0" +paths: {} +components: + schemas: + StringType: + type: string + NumberType: + type: number + MinLength3: + minLength: 3 + ConditionalField: + if: + $ref: '#/components/schemas/StringType' + then: + $ref: '#/components/schemas/MinLength3' + else: + $ref: '#/components/schemas/NumberType' + PaymentInfo: + type: object + properties: + name: + type: string + creditCard: + type: string + billingAddress: + type: string + dependentRequired: + creditCard: + - billingAddress diff --git a/openapi3/testdata/schema31refs.yml b/openapi3/testdata/schema31refs.yml new file mode 100644 index 000000000..cd3ecb947 --- /dev/null +++ b/openapi3/testdata/schema31refs.yml @@ -0,0 +1,70 @@ +openapi: "3.1.0" +info: + title: Test OpenAPI 3.1 Schema Refs + version: "1.0.0" +paths: + /test: + get: + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TupleArray" +components: + schemas: + StringType: + type: string + IntegerType: + type: integer + NamePattern: + type: string + pattern: "^[a-z]+$" + NonNegative: + type: number + minimum: 0 + TupleArray: + type: array + prefixItems: + - $ref: "#/components/schemas/StringType" + - $ref: "#/components/schemas/IntegerType" + ArrayWithContains: + type: array + contains: + $ref: "#/components/schemas/StringType" + ObjectWithPatternProperties: + type: object + patternProperties: + "^x-": + $ref: "#/components/schemas/StringType" + ObjectWithDependentSchemas: + type: object + dependentSchemas: + name: + $ref: "#/components/schemas/NonNegative" + ObjectWithPropertyNames: + type: object + propertyNames: + $ref: "#/components/schemas/NamePattern" + ArrayWithUnevaluatedItems: + type: array + unevaluatedItems: + $ref: "#/components/schemas/StringType" + ObjectWithUnevaluatedProperties: + type: object + unevaluatedProperties: + $ref: "#/components/schemas/StringType" + ObjectWithIfThenElse: + type: object + if: + $ref: "#/components/schemas/StringType" + then: + $ref: "#/components/schemas/IntegerType" + else: + $ref: "#/components/schemas/NonNegative" + StringWithContentSchema: + type: string + contentMediaType: application/json + contentSchema: + $ref: "#/components/schemas/NonNegative" diff --git a/openapi3/v3_apis_guru_openapi_directory_test.go b/openapi3/v3_apis_guru_openapi_directory_test.go new file mode 100644 index 000000000..986a5724e --- /dev/null +++ b/openapi3/v3_apis_guru_openapi_directory_test.go @@ -0,0 +1,204 @@ +package openapi3_test + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "errors" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3conv" +) + +var goldens = filepath.Join("testdata", "apis_guru_openapi_directory") + +func newUnderscorer() *strings.Replacer { + var chars []string + for c := range strings.SplitSeq("\\/_-( ).~", "") { + chars = append(chars, c, "_") + } + return strings.NewReplacer(chars...) +} + +func isOpenAPIVersion(t *testing.T, path, str string) bool { + file, err := os.Open(path) + require.NoError(t, err) + defer file.Close() + + r := bufio.NewScanner(file) + for r.Scan() { + if strings.Contains(r.Text(), str) { + return true + } + } + return false +} + +func shortNameFromPath(path string) string { + shortName := filepath.Base(path) + shortName = strings.TrimSuffix(shortName, "__load") + shortName = strings.TrimSuffix(shortName, "__validate") + shortName = strings.TrimSuffix(shortName, "__validatebis") + return shortName +} + +func golden(t *testing.T, e error, shortName, task string) { + errf := filepath.Join(goldens, shortName+"__"+task) + + if e == nil { + _ = os.Remove(errf) + return + } + + expected, _ := os.ReadFile(errf) + expectedStr := strings.ReplaceAll(string(expected), "\r\n", "\n") + got := strings.ReplaceAll(e.Error(), "\r\n", "\n") + if !strings.HasSuffix(got, "\n") { + got += "\n" + } + + if expectedStr != got { + err := os.WriteFile(errf, []byte(got), 0644) + require.NoError(t, err) + + require.Equal(t, expectedStr, got) + } +} + +func TestV3ApisGuruOpenapiDirectory(t *testing.T) { + if testing.Short() { + t.Skip("skipping APIs Guru's large sets of documents") + } + + commit := os.Getenv("APISGURU_COMMIT") + if commit == "" { + commit = "f7207cf0a5c56081d275ebae4cf615249323385d" // On 2026-04-19 + } + dirName := "APIs-guru-openapi-directory-" + commit[0:7] + targetDir := filepath.Join("testdata", dirName) + + if _, err := os.Stat(targetDir); errors.Is(err, os.ErrNotExist) { + req, err := http.NewRequestWithContext(t.Context(), "GET", "https://github.com/APIs-guru/openapi-directory/tarball/"+commit, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + gzr, err := gzip.NewReader(resp.Body) + require.NoError(t, err) + defer gzr.Close() + tr := tar.NewReader(gzr) + + for { + header, err := tr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + target := filepath.Join(targetDir, header.Name) + switch header.Typeflag { + case tar.TypeDir: + err := os.MkdirAll(target, 0755) + require.NoError(t, err) + case tar.TypeReg: + err := os.MkdirAll(filepath.Dir(target), 0755) + require.NoError(t, err) + + func() { + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + require.NoError(t, err) + defer f.Close() + + _, err = io.Copy(f, tr) + require.NoError(t, err) + }() + } + } + } + + root := filepath.Join(targetDir, dirName, "APIs") + + underscorer := newUnderscorer() + checked := make(map[string]struct{}) + + var paths []string + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + paths = append(paths, path) + } + return nil + }) + require.NoError(t, err) + t.Logf("found %v files in %q", len(paths), root) + + for _, path := range paths { + shortName := underscorer.Replace(strings.TrimPrefix(path, root)[1:]) + + if isOpenAPIVersion(t, path, "openapi: 3") { + t.Run(shortName, func(t *testing.T) { + if disabled(shortName) { + t.SkipNow() + return + } + checked[shortName] = struct{}{} + t.Parallel() + + loader := openapi3.NewLoader() + loader.Context = t.Context() + + doc, err := loader.LoadFromFile(path) + golden(t, err, shortName, "load") + if doc != nil { + var opts []openapi3.ValidationOption + err = doc.Validate(loader.Context, opts...) + golden(t, err, shortName, "validate") + + if err == nil { + openapi3conv.Upgrade(doc, openapi3conv.WithWriter(t.Output())) + err = doc.Validate(loader.Context, opts...) + golden(t, err, shortName, "validatebis") + } + } + }) + } + } + + err = filepath.Walk(goldens, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + shortName := shortNameFromPath(path) + delete(checked, shortName) + } + return nil + }) + require.NoError(t, err) + + files, err := filepath.Glob(goldens + "*") + require.NoError(t, err) + for _, file := range files { + shortName := shortNameFromPath(file) + if _, ok := checked[shortName]; ok || disabled(shortName) { + err := os.Remove(file) + require.NoError(t, err) + } + } + +} + +func disabled(shortName string) bool { + switch shortName { + case "vvv keep these", + "^^^ lines sorted": + return true + } + return false +} diff --git a/openapi3/validate_multi_error_test.go b/openapi3/validate_multi_error_test.go new file mode 100644 index 000000000..b3ce1e306 --- /dev/null +++ b/openapi3/validate_multi_error_test.go @@ -0,0 +1,200 @@ +package openapi3_test + +import ( + "context" + "errors" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +// twoBadPathsSpec is a document with two independent path-level problems: +// both operations are missing the required "responses" object. +const twoBadPathsSpec = ` +openapi: 3.0.0 +info: { title: t, version: "1" } +paths: + /a: + get: {} + /b: + get: {} +` + +// twoBadSectionsSpec has problems in two different document sections. +const twoBadSectionsSpec = ` +openapi: 3.0.0 +info: { title: t, version: "1" } +paths: + /a: + get: {} +components: + schemas: + "bad name with spaces": + type: string +` + +func loadDoc(t *testing.T, src string) *openapi3.T { + t.Helper() + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(src)) + require.NoError(t, err) + return doc +} + +// countLeaves returns the number of non-MultiError leaves in err, walking +// through MultiError nodes and Unwrap chains. The validation tree groups +// per-section MultiErrors under SectionValidationError wrappers, so counting +// leaves gives the total number of independent problems regardless of shape. +func countLeaves(err error) int { + if err == nil { + return 0 + } + if me, ok := err.(openapi3.MultiError); ok { + n := 0 + for _, e := range me { + n += countLeaves(e) + } + return n + } + if u, ok := err.(interface{ Unwrap() error }); ok { + if inner := u.Unwrap(); inner != nil { + return countLeaves(inner) + } + } + return 1 +} + +func TestValidate_MultiError_Off_PreservesFailFast(t *testing.T) { + // Without EnableMultiError, Validate returns the first error and stops. + // The returned error is a single typed error, not a MultiError. + doc := loadDoc(t, twoBadPathsSpec) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var me openapi3.MultiError + require.False(t, errors.As(err, &me), "without EnableMultiError, result must not be a MultiError") +} + +func TestValidate_MultiError_On_AggregatesAcrossPaths(t *testing.T) { + // With EnableMultiError, Validate aggregates problems across paths. + // The result is a tree: T-level MultiError -> SectionValidationError("paths") + // -> Paths-level MultiError -> PathValidationError per bad path. Walking + // the tree must yield one leaf per independent problem. + doc := loadDoc(t, twoBadPathsSpec) + err := doc.Validate(context.Background(), openapi3.EnableMultiError()) + require.Error(t, err) + + var me openapi3.MultiError + require.True(t, errors.As(err, &me), "expected MultiError") + require.Equal(t, 2, countLeaves(err), "expected one leaf per bad path") + + require.ErrorContains(t, err, "/a") + require.ErrorContains(t, err, "/b") + // ErrorContains can't express an exact count, so for the "each defect + // mentions 'responses' exactly twice" check we materialize the error + // string into a local first; CI grep rejects require lines that read + // the error string inline. + combined := err.Error() + require.Equal(t, 2, strings.Count(combined, "responses"), + "each defect should mention the missing 'responses' object") +} + +func TestValidate_MultiError_On_AggregatesAcrossSections(t *testing.T) { + // Problems in different document sections are also aggregated. + doc := loadDoc(t, twoBadSectionsSpec) + err := doc.Validate(context.Background(), openapi3.EnableMultiError()) + require.Error(t, err) + + var me openapi3.MultiError + require.True(t, errors.As(err, &me), "expected MultiError") + require.Equal(t, 2, len(me), + "expected one error per affected section") + + // Confirm both sections are represented in the chain. + var foundComponents, foundPaths bool + for _, e := range me { + var sec *openapi3.SectionValidationError + if errors.As(e, &sec) { + switch sec.Section { + case "components": + foundComponents = true + case "paths": + foundPaths = true + } + } + } + require.True(t, foundComponents, "components section error missing") + require.True(t, foundPaths, "paths section error missing") +} + +func TestValidate_MultiError_On_SingleError_StillReturnsMultiError(t *testing.T) { + // With EnableMultiError, even a single-defect spec returns a MultiError + // (containing one element). MultiError.Error() of a single element is + // byte-identical to the contained error's Error(), so the string output + // is unchanged; only the static type differs. + const oneBadPathSpec = ` +openapi: 3.0.0 +info: { title: t, version: "1" } +paths: + /a: + get: {} +` + doc := loadDoc(t, oneBadPathSpec) + err := doc.Validate(context.Background(), openapi3.EnableMultiError()) + require.Error(t, err) + + var me openapi3.MultiError + require.True(t, errors.As(err, &me)) + require.Len(t, me, 1) + + // MultiError.Is / MultiError.As walk into the contained errors, so typed + // consumers using errors.As keep working seamlessly. + var sec *openapi3.SectionValidationError + require.True(t, errors.As(err, &sec), "errors.As should walk through MultiError") +} + +func TestValidate_MultiError_On_NoErrors_ReturnsNil(t *testing.T) { + // A well-formed document still returns nil, regardless of the option. + const goodSpec = ` +openapi: 3.0.0 +info: { title: t, version: "1" } +paths: + /a: + get: + responses: + "200": + description: ok +` + doc := loadDoc(t, goodSpec) + require.NoError(t, doc.Validate(context.Background(), openapi3.EnableMultiError())) +} + +// TestResponses_Validate_EmptyAndExtensionAggregate pins a regression caught +// in review: Responses.Validate previously short-circuited with +// "return me.result()" right after emitting the empty-responses finding, +// which meant validateExtensions never ran on empty responses. Under +// multi-error mode, an empty Responses with a non-x- sibling key on its +// Extensions map must surface BOTH findings, not just the first one. +func TestResponses_Validate_EmptyAndExtensionAggregate(t *testing.T) { + responses := openapi3.NewResponses() + require.Equal(t, 0, responses.Len(), "fixture: Responses must be empty for this test") + // Non-x- key in Extensions triggers validateExtensions's + // "extra sibling fields" error. + responses.Extensions = map[string]any{"bogus-sibling": "anything"} + + ctx := openapi3.WithValidationOptions(context.Background(), openapi3.EnableMultiError()) + err := responses.Validate(ctx) + require.Error(t, err) + + var me openapi3.MultiError + require.True(t, errors.As(err, &me), "expected MultiError under EnableMultiError") + require.Equal(t, 2, countLeaves(err), + "expected one leaf for empty-responses and one for the bogus extension key") + + combined := err.Error() + require.Contains(t, combined, "the responses object MUST contain at least one response code") + require.Contains(t, combined, "bogus-sibling") +} diff --git a/openapi3/validation_error.go b/openapi3/validation_error.go new file mode 100644 index 000000000..8929a1f6e --- /dev/null +++ b/openapi3/validation_error.go @@ -0,0 +1,1568 @@ +package openapi3 + +import "fmt" + +// ValidationError is the embedded base for every typed validation error +// emitted by the document validation walker (T.Validate, Info.Validate, +// Paths.Validate, etc.). Four categories of typed error are exposed; +// pick whichever the caller needs: +// +// 1. Base — *ValidationError. Catchall for "this is a validation +// issue, here is the message". Reachable from any leaf via the As +// method that each leaf implements. +// 2. Cluster — types like *RequiredFieldError or +// *FieldVersionMismatchError. Group families of related failures +// and expose the family-level metadata (Field, MinVersion, ...). +// Wrap the underlying leaf via Unwrap, so errors.As can still walk +// to the leaf. Some clusters are single-site (e.g. *SchemaTypeError) +// and carry only their own fields with no separate leaf. +// 3. Leaf — one type per call site (e.g. *InfoVersionRequired, +// *LicenseIdentifierFieldFor31Plus). Lets callers match an exact +// failure point without string comparison. +// 4. Context wrapper — types like *SectionValidationError, +// *PathValidationError, *ParameterFieldValidationError. Add scope +// ("which section", "which path", "which parameter") around an +// inner error chain but do NOT themselves report a failure +// condition — the actual error lives in Cause. Defined in +// validation_error_context.go; see that file's header for the +// full inventory and conventions. +// +// All four are reachable from the same returned error through +// standard Go error wrapping (errors.As, errors.Is, errors.Unwrap), +// so a caller that only needs "is it a validation error?" stops at +// the base and a caller that wants "is it specifically license.identifier +// being used in 3.0?" matches the leaf. A caller that wants "which +// section did this happen in?" matches the context wrapper and walks +// further for the cluster/leaf. +// +// A canonical error chain therefore looks like: +// +// ComponentValidationError{Section: "schema", Name: "Foo"} +// -> RequiredFieldError{Field: "type"} +// -> SchemaTypeRequired{Message: "..."} +// +// Context wrapper carries WHERE, cluster carries WHAT category, leaf +// carries EXACTLY WHICH case. +// +// Backward compatibility: every site that today returns errors.New(msg) +// migrates to a leaf type that embeds ValidationError with Message set +// to the original string. (*ValidationError).Error() returns Message +// unchanged, so existing string-matching consumers see identical output. +type ValidationError struct { + Message string +} + +func (e *ValidationError) Error() string { return e.Message } + +// asValidationError is a small helper used by every leaf type's As +// method to expose the embedded *ValidationError to errors.As. Defined +// once here so the leaf-side boilerplate stays a single line. +func asValidationError(target any, ve *ValidationError) bool { + t, ok := target.(**ValidationError) + if !ok { + return false + } + *t = ve + return true +} + +// --------------------------------------------------------------------- +// Cluster types — group families of related failures. +// --------------------------------------------------------------------- + +// RequiredFieldError clusters "X must be a non-empty value" failures +// across the spec (info.title, info.version, license.name, the openapi +// version string, server.url). Carries the field path, wraps the +// per-site leaf so callers can match either: +// +// var rfe *RequiredFieldError +// if errors.As(err, &rfe) { /* knows the field */ } +// +// var ivr *InfoVersionRequired +// if errors.As(err, &ivr) { /* knows it's exactly info.version */ } +type RequiredFieldError struct { + // Field is the JSON-pointer-style path of the required field + // (e.g. "info.version", "license.name", "openapi", "server.url"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil for + // document-root fields (Loader doesn't track Origin on *T) and on + // loads where origin tracking was off. + Origin *Origin +} + +func (e *RequiredFieldError) Error() string { return e.Cause.Error() } +func (e *RequiredFieldError) Unwrap() error { return e.Cause } + +// SchemaValueError clusters failures of "this schema's value +// doesn't satisfy the schema's own constraints" — example, default, +// examples[i], etc. checked against the schema during document +// validation. Wraps the underlying error from VisitJSON (a +// *SchemaError or a MultiError of them) so callers can match either: +// +// var sve *SchemaValueError +// if errors.As(err, &sve) { /* knows ValueKind = "example" */ } +// +// var se *SchemaError +// if errors.As(err, &se) { /* full schema-validation detail */ } +// +// Cause is typed as error (not *SchemaError) because VisitJSON can +// return either a single SchemaError or a MultiError aggregating +// several. errors.As walks both shapes transparently. +type SchemaValueError struct { + // ValueKind identifies the schema sub-field whose value failed + // (e.g. "example", "default"). + ValueKind string + // Cause is the underlying error from schema.VisitJSON — either a + // *SchemaError or a MultiError of them. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil when + // origin tracking is off. + Origin *Origin +} + +func (e *SchemaValueError) Error() string { + return fmt.Sprintf("invalid %s: %s", e.ValueKind, e.Cause.Error()) +} + +func (e *SchemaValueError) Unwrap() error { return e.Cause } + +// PathParametersError clusters "operation declares fewer/more path +// parameters than appear in the path template" failures. Carries the +// path template, method, and the list of missing parameter names so +// callers can render or filter without parsing the message. +type PathParametersError struct { + // Path is the path template (e.g. "/api/{domain}/{project}/..."). + Path string + // Method is the HTTP method (e.g. "POST"). + Method string + // Missing names path-template variables (or operation parameters) + // that don't have a corresponding declaration on the other side. + Missing []string + // Origin is the source location of the path item when the document + // was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *PathParametersError) Error() string { + return fmt.Sprintf("operation %s %s must define exactly all path parameters (missing: %v)", + e.Method, e.Path, e.Missing) +} + +// FieldVersionMismatchError clusters "field X is for OpenAPI >=Y" +// failures (3.1+ keywords used in 3.0 documents). Carries the field +// name and minimum version, wraps the per-site leaf. +type FieldVersionMismatchError struct { + // Field is the field name flagged (e.g. "summary", "identifier", + // "$defs", "prefixItems", "contains", ...). + Field string + // MinVersion is the minimum OpenAPI version that allows the field + // (e.g. "3.1"). + MinVersion string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. Nil for + // document-root fields (Loader doesn't track Origin on *T) and on + // loads where origin tracking was off. + Origin *Origin +} + +func (e *FieldVersionMismatchError) Error() string { return e.Cause.Error() } +func (e *FieldVersionMismatchError) Unwrap() error { return e.Cause } + +// ServerURLTemplateError clusters server URL template failures — +// mismatched braces and undeclared variables (template variables not +// matched by Server.Variables, or vice versa). +type ServerURLTemplateError struct { + // URL is the server URL whose template failed validation. + URL string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *ServerURLTemplateError) Error() string { return e.Cause.Error() } +func (e *ServerURLTemplateError) Unwrap() error { return e.Cause } + +// EitherFieldRequiredError clusters "at least one of these fields must +// be set" failures (example.value vs externalValue, link.operationId +// vs operationRef). +type EitherFieldRequiredError struct { + // Fields is the set of field names, at least one of which must be + // set (e.g. ["value", "externalValue"]). + Fields []string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *EitherFieldRequiredError) Error() string { return e.Cause.Error() } +func (e *EitherFieldRequiredError) Unwrap() error { return e.Cause } + +// SchemaBothFormsExclusive clusters "this union-typed schema field is +// set to both its boolean and schema forms simultaneously" failures — +// additionalProperties, unevaluatedItems, unevaluatedProperties. +type SchemaBothFormsExclusive struct { + // Field is the name of the union-typed schema property + // (e.g. "additionalProperties", "unevaluatedItems"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *SchemaBothFormsExclusive) Error() string { return e.Cause.Error() } +func (e *SchemaBothFormsExclusive) Unwrap() error { return e.Cause } + +// ExactlyOneFieldError clusters "exactly one of these fields must be +// set" failures — e.g. a parameter or header where neither content +// nor schema is set, or both are. +type ExactlyOneFieldError struct { + // Fields is the set of fields, exactly one of which must be set + // (e.g. ["content", "schema"]). + Fields []string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *ExactlyOneFieldError) Error() string { return e.Cause.Error() } +func (e *ExactlyOneFieldError) Unwrap() error { return e.Cause } + +// SingleEntryContentError clusters "the content map must contain at +// most one entry" failures (parameter.content, header.content). +type SingleEntryContentError struct { + // Subject is the kind of object whose Content map is too large + // ("parameter", "header"). + Subject string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *SingleEntryContentError) Error() string { return e.Cause.Error() } +func (e *SingleEntryContentError) Unwrap() error { return e.Cause } + +// WebhookNilError clusters "the value at webhook key X is nil" +// failures from T.Validate's webhook walk. Carries the offending +// key name. +type WebhookNilError struct { + // Name is the webhook key whose value was nil. + Name string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error +} + +func (e *WebhookNilError) Error() string { return e.Cause.Error() } +func (e *WebhookNilError) Unwrap() error { return e.Cause } + +// MutuallyExclusiveFieldsError clusters "fields X and Y are both set, +// only one is allowed" failures (example.value vs externalValue, +// mediaType.example vs examples, license.url vs identifier, +// link.operationId vs operationRef). Carries both field names and +// wraps the per-site leaf. +type MutuallyExclusiveFieldsError struct { + // Field1 and Field2 name the two fields the spec forbids setting + // together (e.g. "value", "externalValue"). + Field1 string + Field2 string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *MutuallyExclusiveFieldsError) Error() string { return e.Cause.Error() } +func (e *MutuallyExclusiveFieldsError) Unwrap() error { return e.Cause } + +// ForbiddenFieldError clusters "field X must not be set in this +// context" failures (header.name and header.in inside a Headers map, +// OAuth flow URLs that don't apply to the chosen flow type). +type ForbiddenFieldError struct { + // Field is the name of the forbidden field (e.g. "name", "in", + // "authorizationUrl", "tokenUrl"). + Field string + // Cause is the underlying leaf error. Walked by errors.Unwrap. + Cause error + // Origin is the source location of the offending element when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *ForbiddenFieldError) Error() string { return e.Cause.Error() } +func (e *ForbiddenFieldError) Unwrap() error { return e.Cause } + +// PathParameterRequiredError clusters "path parameter X must be required" +// failures: per the OpenAPI spec, every parameter with `in: path` must be +// declared with `required: true`. Carries the parameter name so callers +// can render or filter by it. +type PathParameterRequiredError struct { + // Param is the path-parameter name (e.g. "groupId"). + Param string + // Origin is the source location of the offending parameter when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *PathParameterRequiredError) Error() string { + return fmt.Sprintf("path parameter %q must be required", e.Param) +} + +// DuplicateOperationIDError clusters "two operations share an operationId" +// failures. operationIds must be unique across all paths in a document. +// Endpoints are rendered as " " (e.g. "POST /things"). +type DuplicateOperationIDError struct { + // OperationID is the duplicated operationId value. + OperationID string + // Endpoint1 / Endpoint2 are the two offending endpoints, in + // deterministic order (lexicographically) for stable error messages. + Endpoint1 string + Endpoint2 string + // Origin is the source location of the second (offending) operation + // when the document was loaded with Loader.IncludeOrigin = true. The + // pre-existing Endpoint1 is implicitly fine; the duplicate landed at + // Endpoint2's site, which is the natural "go fix this" pointer. + Origin *Origin +} + +func (e *DuplicateOperationIDError) Error() string { + return fmt.Sprintf("operations %q and %q have the same operation id %q", + e.Endpoint1, e.Endpoint2, e.OperationID) +} + +// ExtraSiblingFieldsError clusters "unexpected sibling fields" failures. +// Most commonly this fires when fields appear alongside a $ref that the +// OpenAPI spec doesn't allow there, or as unknown keys on objects whose +// only permitted extras are `x-` extensions. Carries the offending field +// names so callers can render or filter. +type ExtraSiblingFieldsError struct { + // Fields is the list of unexpected sibling field names. + Fields []string + // Origin is the source location of the parent object that carries + // the extra siblings when the document was loaded with + // Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *ExtraSiblingFieldsError) Error() string { + return fmt.Sprintf("extra sibling fields: %+v", e.Fields) +} + +// SchemaTypeError clusters "unsupported 'type' value" failures on a +// Schema. Carries the bad type value so callers can surface it in +// user-facing output and filter findings by it. +type SchemaTypeError struct { + // Type is the rejected type value (e.g. "bool", "int", "http"). + Type string + // Origin is the source location of the offending schema when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *SchemaTypeError) Error() string { + return fmt.Sprintf("unsupported 'type' value %q", e.Type) +} + +// InvalidParameterInError clusters "parameter can't have 'in' value X" +// failures. The OpenAPI 3.x spec accepts only `path`, `query`, `header`, +// or `cookie`; this fires when a parameter declares anything else +// (commonly `body`, a Swagger 2.0 leftover). +type InvalidParameterInError struct { + // Value is the rejected `in:` value (e.g. "body", "formData"). + Value string + // Origin is the source location of the offending parameter when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *InvalidParameterInError) Error() string { + return fmt.Sprintf("parameter can't have 'in' value %q", e.Value) +} + +// SchemaPatternRegexError clusters "schema pattern failed to compile" +// failures. Kin's regex engine is Go's RE2, which rejects Perl features +// like `(?!...)` lookahead; specs that leak Perl regex patterns (common +// in AWS-style auto-generated schemas) trip this. Carries the offending +// pattern as a structured field for typed dispatch, while preserving +// the underlying SchemaError's rendered message byte-for-byte (Error() +// delegates to Cause.Error()) so existing string-based consumers and +// golden fixtures are unaffected. +type SchemaPatternRegexError struct { + // Pattern is the schema's `pattern:` value that failed to compile. + Pattern string + // Cause is the underlying error (a *SchemaError wrapping the + // regexp package's syntax error). Unwrap returns this so callers + // walking the error chain see the SchemaError. + Cause error + // Origin is the source location of the offending schema when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *SchemaPatternRegexError) Error() string { return e.Cause.Error() } + +func (e *SchemaPatternRegexError) Unwrap() error { return e.Cause } + +// InvalidSecuritySchemeTypeError clusters "security scheme 'type' can't +// be X" failures. The OpenAPI 3.x spec accepts only `apiKey`, `http`, +// `oauth2`, `openIdConnect`, and `mutualTLS` (3.1+); this fires when a +// security scheme declares anything else. +type InvalidSecuritySchemeTypeError struct { + // Type is the rejected type value (e.g. "cookie", "saml"). + Type string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *InvalidSecuritySchemeTypeError) Error() string { + return fmt.Sprintf("security scheme 'type' can't be %q", e.Type) +} + +// InvalidHTTPSchemeError clusters "security scheme of type 'http' has +// invalid 'scheme' value X" failures. The OpenAPI/HTTP-auth registry +// accepts only `bearer`, `basic`, `negotiate`, `digest`; this fires +// when an http scheme declares anything else. +type InvalidHTTPSchemeError struct { + // Scheme is the rejected scheme value (e.g. "mutual", "oauth"). + Scheme string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *InvalidHTTPSchemeError) Error() string { + return fmt.Sprintf("security scheme of type 'http' has invalid 'scheme' value %q", e.Scheme) +} + +// UnresolvedRefError clusters "found unresolved ref: X" failures fired +// by the loader when a $ref cannot be resolved against the loaded +// document. Carries the offending ref string so callers can surface +// it in user-facing output and filter findings by it. +type UnresolvedRefError struct { + // Ref is the unresolved $ref value (e.g. "#/components/schemas/X" + // or "external.yaml#/..."). + Ref string + // Origin is the source location of the ref-bearing object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *UnresolvedRefError) Error() string { + return fmt.Sprintf("found unresolved ref: %q", e.Ref) +} + +// APIKeyInInvalidError clusters "apiKey should have 'in'. It can be +// 'query', 'header' or 'cookie', not X" failures. Fires when an +// apiKey security scheme either omits `in:` or sets it to a value +// outside {query, header, cookie}. Carries the rejected value so +// callers can render or filter; empty string means the field was +// missing entirely. +type APIKeyInInvalidError struct { + // Value is the rejected `in:` value (empty when the field was + // omitted, otherwise the bad value e.g. "body"). + Value string + // Origin is the source location of the offending security scheme + // when the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *APIKeyInInvalidError) Error() string { + return fmt.Sprintf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", e.Value) +} + +// PathMustStartWithSlashError clusters "path X does not start with a +// forward slash" failures. Path keys in the paths object must begin +// with `/`. +type PathMustStartWithSlashError struct { + // Path is the offending path key (e.g. "users/{id}"). + Path string + // Origin is the source location of the paths object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *PathMustStartWithSlashError) Error() string { + return fmt.Sprintf("path %q does not start with a forward slash (/)", e.Path) +} + +// ConflictingPathsError clusters "conflicting paths X and Y" failures. +// Fires when two path keys normalize to the same template (e.g. +// "/users/{a}" and "/users/{b}" both normalize to "/users/{}"). +type ConflictingPathsError struct { + // Path1 / Path2 are the two conflicting path keys, in document + // order. + Path1 string + Path2 string + // Origin is the source location of the paths object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *ConflictingPathsError) Error() string { + return fmt.Sprintf("conflicting paths %q and %q", e.Path1, e.Path2) +} + +// DuplicateParameterError clusters "more than one X parameter has +// name Y" failures. Fires when two parameters on an operation (or +// path item) share the same In + Name combination. +type DuplicateParameterError struct { + // In is the parameter location (e.g. "query", "path", "header"). + In string + // Name is the duplicated parameter name. + Name string + // Origin is the source location of the offending parameter when + // the document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *DuplicateParameterError) Error() string { + return fmt.Sprintf("more than one %q parameter has name %q", e.In, e.Name) +} + +// InvalidSerializationMethodError clusters "serialization method with +// style=X and explode=Y is not supported by Z" failures. Fires for +// invalid (style, explode) combinations on encodings, parameters, +// and headers. The Subject discriminates which surface is reporting: +// "media type" for encoding, the parameter location ("path", +// "query", etc.) for parameters and "header" for headers. +type InvalidSerializationMethodError struct { + // Subject discriminates the calling surface ("media type", + // "path"/"query"/"header"/"cookie" for parameters, or "header" + // for the header.go site). + Subject string + // Style is the offending `style:` value. + Style string + // Explode is the offending `explode:` value. + Explode bool + // Origin is the source location of the offending object when the + // document was loaded with Loader.IncludeOrigin = true. + Origin *Origin +} + +func (e *InvalidSerializationMethodError) Error() string { + if e.Subject == "media type" { + return fmt.Sprintf("serialization method with style=%q and explode=%v is not supported by media type", e.Style, e.Explode) + } + return fmt.Sprintf("serialization method with style=%q and explode=%v is not supported by a %s parameter", e.Style, e.Explode, e.Subject) +} + +// --------------------------------------------------------------------- +// Leaf types — one per call site. Each embeds ValidationError for +// Error() and As-to-base, and is wrapped in its cluster type when +// returned from a validator. +// +// Naming convention: for required fields, +// FieldFor31Plus for 3.1-only fields used in 3.0 docs. +// Subjects use Go-identifier-friendly transliterations of OAS field +// paths ("$defs" -> "Defs", "$dynamicAnchor" -> "DynamicAnchor"). +// --------------------------------------------------------------------- + +// RequiredFieldError leaves. + +type InfoVersionRequired struct{ ValidationError } + +func (e *InfoVersionRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type InfoTitleRequired struct{ ValidationError } + +func (e *InfoTitleRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type LicenseNameRequired struct{ ValidationError } + +func (e *LicenseNameRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OpenAPIVersionRequired struct{ ValidationError } + +func (e *OpenAPIVersionRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ServerURLRequired struct{ ValidationError } + +func (e *ServerURLRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// ServerURLTemplateError leaves. + +type ServerURLMismatchedBraces struct{ ValidationError } + +func (e *ServerURLMismatchedBraces) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ServerURLUndeclaredVariables struct{ ValidationError } + +func (e *ServerURLUndeclaredVariables) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SchemaItemsRequired struct{ ValidationError } + +func (e *SchemaItemsRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// EitherFieldRequiredError leaves. + +type ExampleValueOrExternalValueRequired struct{ ValidationError } + +func (e *ExampleValueOrExternalValueRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type LinkOperationIDOrRefRequired struct{ ValidationError } + +func (e *LinkOperationIDOrRefRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type InfoRequired struct{ ValidationError } + +func (e *InfoRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type PathsRequired struct{ ValidationError } + +func (e *PathsRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type JSONSchemaDialectAbsoluteURIRequired struct{ ValidationError } + +func (e *JSONSchemaDialectAbsoluteURIRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// SchemaBothFormsExclusive leaves. + +type SchemaAdditionalPropertiesBothForms struct{ ValidationError } + +func (e *SchemaAdditionalPropertiesBothForms) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SchemaUnevaluatedItemsBothForms struct{ ValidationError } + +func (e *SchemaUnevaluatedItemsBothForms) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SchemaUnevaluatedPropertiesBothForms struct{ ValidationError } + +func (e *SchemaUnevaluatedPropertiesBothForms) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// ExactlyOneFieldError leaves. + +type ParameterContentSchemaExactlyOne struct{ ValidationError } + +func (e *ParameterContentSchemaExactlyOne) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type HeaderContentSchemaExactlyOne struct{ ValidationError } + +func (e *HeaderContentSchemaExactlyOne) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// SingleEntryContentError leaves. + +type ParameterContentSingleEntry struct{ ValidationError } + +func (e *ParameterContentSingleEntry) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type HeaderContentSingleEntry struct{ ValidationError } + +func (e *HeaderContentSingleEntry) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// WebhookNilError leaf. + +type WebhookNil struct{ ValidationError } + +func (e *WebhookNil) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// SecurityScheme leaves wrapped in RequiredFieldError / ForbiddenFieldError. + +type OpenIDConnectURLRequired struct{ ValidationError } + +func (e *OpenIDConnectURLRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SecuritySchemeFlowsRequired struct{ ValidationError } + +func (e *SecuritySchemeFlowsRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SecuritySchemeInForbidden struct{ ValidationError } + +func (e *SecuritySchemeInForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SecuritySchemeNameForbidden struct{ ValidationError } + +func (e *SecuritySchemeNameForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SecuritySchemeBearerFormatForbidden struct{ ValidationError } + +func (e *SecuritySchemeBearerFormatForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SecuritySchemeFlowsForbidden struct{ ValidationError } + +func (e *SecuritySchemeFlowsForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ParameterExampleAndExamplesExclusive struct{ ValidationError } + +func (e *ParameterExampleAndExamplesExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ServerVariableDefaultRequired struct{ ValidationError } + +func (e *ServerVariableDefaultRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ExternalDocsURLRequired struct{ ValidationError } + +func (e *ExternalDocsURLRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OperationResponsesRequired struct{ ValidationError } + +func (e *OperationResponsesRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type RequestBodyContentRequired struct{ ValidationError } + +func (e *RequestBodyContentRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ResponseDescriptionRequired struct{ ValidationError } + +func (e *ResponseDescriptionRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OAuthFlowScopesRequired struct{ ValidationError } + +func (e *OAuthFlowScopesRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OAuthFlowAuthorizationURLRequired struct{ ValidationError } + +func (e *OAuthFlowAuthorizationURLRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OAuthFlowTokenURLRequired struct{ ValidationError } + +func (e *OAuthFlowTokenURLRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ParameterNameRequired struct{ ValidationError } + +func (e *ParameterNameRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ResponsesNonEmptyRequired struct{ ValidationError } + +func (e *ResponsesNonEmptyRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type APIKeySecuritySchemeNameRequired struct{ ValidationError } + +func (e *APIKeySecuritySchemeNameRequired) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// MutuallyExclusiveFieldsError leaves. + +type ExampleValueExternalValueExclusive struct{ ValidationError } + +func (e *ExampleValueExternalValueExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type MediaTypeExampleExamplesExclusive struct{ ValidationError } + +func (e *MediaTypeExampleExamplesExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type LicenseURLIdentifierExclusive struct{ ValidationError } + +func (e *LicenseURLIdentifierExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type LinkOperationIDRefExclusive struct{ ValidationError } + +func (e *LinkOperationIDRefExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SchemaReadOnlyWriteOnlyExclusive struct{ ValidationError } + +func (e *SchemaReadOnlyWriteOnlyExclusive) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// ForbiddenFieldError leaves. + +type HeaderNameForbidden struct{ ValidationError } + +func (e *HeaderNameForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type HeaderInForbidden struct{ ValidationError } + +func (e *HeaderInForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OAuthFlowAuthorizationURLForbidden struct{ ValidationError } + +func (e *OAuthFlowAuthorizationURLForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type OAuthFlowTokenURLForbidden struct{ ValidationError } + +func (e *OAuthFlowTokenURLForbidden) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// FieldVersionMismatchError leaves — non-schema fields. + +type InfoSummaryFieldFor31Plus struct{ ValidationError } + +func (e *InfoSummaryFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type LicenseIdentifierFieldFor31Plus struct{ ValidationError } + +func (e *LicenseIdentifierFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type WebhooksFieldFor31Plus struct{ ValidationError } + +func (e *WebhooksFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type JSONSchemaDialectFieldFor31Plus struct{ ValidationError } + +func (e *JSONSchemaDialectFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// FieldVersionMismatchError leaves — schema fields (rejected by +// schema.go's reject() helper when a 3.0 doc uses 3.1 keywords). + +type ConstFieldFor31Plus struct{ ValidationError } + +func (e *ConstFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ExamplesFieldFor31Plus struct{ ValidationError } + +func (e *ExamplesFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type PrefixItemsFieldFor31Plus struct{ ValidationError } + +func (e *PrefixItemsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ContainsFieldFor31Plus struct{ ValidationError } + +func (e *ContainsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type MinContainsFieldFor31Plus struct{ ValidationError } + +func (e *MinContainsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type MaxContainsFieldFor31Plus struct{ ValidationError } + +func (e *MaxContainsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type PatternPropertiesFieldFor31Plus struct{ ValidationError } + +func (e *PatternPropertiesFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type DependentSchemasFieldFor31Plus struct{ ValidationError } + +func (e *DependentSchemasFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type PropertyNamesFieldFor31Plus struct{ ValidationError } + +func (e *PropertyNamesFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type UnevaluatedItemsFieldFor31Plus struct{ ValidationError } + +func (e *UnevaluatedItemsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type UnevaluatedPropertiesFieldFor31Plus struct{ ValidationError } + +func (e *UnevaluatedPropertiesFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type IfFieldFor31Plus struct{ ValidationError } + +func (e *IfFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ThenFieldFor31Plus struct{ ValidationError } + +func (e *ThenFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ElseFieldFor31Plus struct{ ValidationError } + +func (e *ElseFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type DependentRequiredFieldFor31Plus struct{ ValidationError } + +func (e *DependentRequiredFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ContentEncodingFieldFor31Plus struct{ ValidationError } + +func (e *ContentEncodingFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ContentMediaTypeFieldFor31Plus struct{ ValidationError } + +func (e *ContentMediaTypeFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type ContentSchemaFieldFor31Plus struct{ ValidationError } + +func (e *ContentSchemaFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type DefsFieldFor31Plus struct{ ValidationError } + +func (e *DefsFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type SchemaFieldFor31Plus struct{ ValidationError } + +func (e *SchemaFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type CommentFieldFor31Plus struct{ ValidationError } + +func (e *CommentFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type IDFieldFor31Plus struct{ ValidationError } + +func (e *IDFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type AnchorFieldFor31Plus struct{ ValidationError } + +func (e *AnchorFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type DynamicAnchorFieldFor31Plus struct{ ValidationError } + +func (e *DynamicAnchorFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +type DynamicRefFieldFor31Plus struct{ ValidationError } + +func (e *DynamicRefFieldFor31Plus) As(target any) bool { + return asValidationError(target, &e.ValidationError) +} + +// --------------------------------------------------------------------- +// Constructors — the validator-side entry points. Build the leaf, wrap +// in the cluster, return the cluster (which exposes the leaf via +// Unwrap and the base via the leaf's As method). +// --------------------------------------------------------------------- + +func newRequiredField(field string, leaf error, origin *Origin) error { + return &RequiredFieldError{Field: field, Cause: leaf, Origin: origin} +} + +func newInfoVersionRequired(origin *Origin) error { + return newRequiredField("info.version", + &InfoVersionRequired{ValidationError{Message: "value of version must be a non-empty string"}}, origin) +} + +func newInfoTitleRequired(origin *Origin) error { + return newRequiredField("info.title", + &InfoTitleRequired{ValidationError{Message: "value of title must be a non-empty string"}}, origin) +} + +func newLicenseNameRequired(origin *Origin) error { + return newRequiredField("license.name", + &LicenseNameRequired{ValidationError{Message: "value of license name must be a non-empty string"}}, origin) +} + +func newOpenAPIVersionRequired(origin *Origin) error { + return newRequiredField("openapi", + &OpenAPIVersionRequired{ValidationError{Message: "value of openapi must be a non-empty string"}}, origin) +} + +func newServerURLRequired(origin *Origin) error { + return newRequiredField("server.url", + &ServerURLRequired{ValidationError{Message: "value of url must be a non-empty string"}}, origin) +} + +// newServerURLTemplateError wraps leaf in a *ServerURLTemplateError +// carrying the offending Server.URL. +func newServerURLTemplateError(serverURL string, leaf error, origin *Origin) error { + return &ServerURLTemplateError{URL: serverURL, Cause: leaf, Origin: origin} +} + +func newServerURLMismatchedBraces(serverURL string, origin *Origin) error { + const msg = "server URL has mismatched { and }" + return newServerURLTemplateError(serverURL, + &ServerURLMismatchedBraces{ValidationError{Message: msg}}, origin) +} + +func newServerURLUndeclaredVariables(serverURL string, origin *Origin) error { + const msg = "server has undeclared variables" + return newServerURLTemplateError(serverURL, + &ServerURLUndeclaredVariables{ValidationError{Message: msg}}, origin) +} + +func newSchemaItemsRequired(origin *Origin) error { + const msg = "when schema type is 'array', schema 'items' must be non-null" + return newRequiredField("schema.items", + &SchemaItemsRequired{ValidationError{Message: msg}}, origin) +} + +// newEitherFieldRequired wraps leaf in an *EitherFieldRequiredError +// carrying the set of field names, at least one of which must be set. +func newEitherFieldRequired(fields []string, leaf error, origin *Origin) error { + return &EitherFieldRequiredError{Fields: fields, Cause: leaf, Origin: origin} +} + +func newExampleValueOrExternalValueRequired(origin *Origin) error { + const msg = "no value or externalValue field" + return newEitherFieldRequired([]string{"value", "externalValue"}, + &ExampleValueOrExternalValueRequired{ValidationError{Message: msg}}, origin) +} + +func newLinkOperationIDOrRefRequired(origin *Origin) error { + const msg = "missing operationId or operationRef on link" + return newEitherFieldRequired([]string{"operationId", "operationRef"}, + &LinkOperationIDOrRefRequired{ValidationError{Message: msg}}, origin) +} + +func newInfoRequired(origin *Origin) error { + return newRequiredField("info", + &InfoRequired{ValidationError{Message: "must be an object"}}, origin) +} + +func newPathsRequired(origin *Origin) error { + return newRequiredField("paths", + &PathsRequired{ValidationError{Message: "must be an object"}}, origin) +} + +func newJSONSchemaDialectAbsoluteURIRequired(origin *Origin) error { + return newRequiredField("jsonSchemaDialect", + &JSONSchemaDialectAbsoluteURIRequired{ValidationError{Message: "must be an absolute URI with a scheme"}}, origin) +} + +// newSchemaBothForms wraps leaf in a *SchemaBothFormsExclusive carrying +// the name of the union-typed schema property. +func newSchemaBothForms(field string, leaf error, origin *Origin) error { + return &SchemaBothFormsExclusive{Field: field, Cause: leaf, Origin: origin} +} + +func newSchemaAdditionalPropertiesBothForms(origin *Origin) error { + const msg = "additionalProperties are set to both boolean and schema" + return newSchemaBothForms("additionalProperties", + &SchemaAdditionalPropertiesBothForms{ValidationError{Message: msg}}, origin) +} + +func newSchemaUnevaluatedItemsBothForms(origin *Origin) error { + const msg = "unevaluatedItems is set to both boolean and schema" + return newSchemaBothForms("unevaluatedItems", + &SchemaUnevaluatedItemsBothForms{ValidationError{Message: msg}}, origin) +} + +func newSchemaUnevaluatedPropertiesBothForms(origin *Origin) error { + const msg = "unevaluatedProperties is set to both boolean and schema" + return newSchemaBothForms("unevaluatedProperties", + &SchemaUnevaluatedPropertiesBothForms{ValidationError{Message: msg}}, origin) +} + +// newExactlyOneField wraps leaf in an *ExactlyOneFieldError carrying +// the set of fields, exactly one of which must be set. +func newExactlyOneField(fields []string, leaf error, origin *Origin) error { + return &ExactlyOneFieldError{Fields: fields, Cause: leaf, Origin: origin} +} + +func newParameterContentSchemaExactlyOne(origin *Origin) error { + const msg = "parameter must contain exactly one of content and schema" + return newExactlyOneField([]string{"content", "schema"}, + &ParameterContentSchemaExactlyOne{ValidationError{Message: msg}}, origin) +} + +// newHeaderContentSchemaExactlyOne formats the same way the existing +// header.go site does, including the historical "%v" dump of the +// Header struct, so the Error() string remains byte-identical. +func newHeaderContentSchemaExactlyOne(header any, origin *Origin) error { + msg := fmt.Sprintf("parameter must contain exactly one of content and schema: %v", header) + return newExactlyOneField([]string{"content", "schema"}, + &HeaderContentSchemaExactlyOne{ValidationError{Message: msg}}, origin) +} + +// newSingleEntryContent wraps leaf in a *SingleEntryContentError +// carrying the Subject ("parameter" or "header") whose Content map +// has more than one entry. +func newSingleEntryContent(subject string, leaf error, origin *Origin) error { + return &SingleEntryContentError{Subject: subject, Cause: leaf, Origin: origin} +} + +func newParameterContentSingleEntry(origin *Origin) error { + const msg = "parameter content must only contain one entry" + return newSingleEntryContent("parameter", + &ParameterContentSingleEntry{ValidationError{Message: msg}}, origin) +} + +func newHeaderContentSingleEntry(origin *Origin) error { + const msg = "parameter content must only contain one entry" + return newSingleEntryContent("header", + &HeaderContentSingleEntry{ValidationError{Message: msg}}, origin) +} + +func newWebhookNil(name string) error { + msg := fmt.Sprintf("webhook %q is nil", name) + return &WebhookNilError{ + Name: name, + Cause: &WebhookNil{ValidationError{Message: msg}}, + } +} + +func newExternalDocsURLRequired(origin *Origin) error { + return newRequiredField("externalDocs.url", + &ExternalDocsURLRequired{ValidationError{Message: "url is required"}}, origin) +} + +func newOperationResponsesRequired(origin *Origin) error { + return newRequiredField("operation.responses", + &OperationResponsesRequired{ValidationError{Message: "value of responses must be an object"}}, origin) +} + +func newRequestBodyContentRequired(origin *Origin) error { + return newRequiredField("requestBody.content", + &RequestBodyContentRequired{ValidationError{Message: "content of the request body is required"}}, origin) +} + +func newResponseDescriptionRequired(origin *Origin) error { + return newRequiredField("response.description", + &ResponseDescriptionRequired{ValidationError{Message: "a short description of the response is required"}}, origin) +} + +func newOAuthFlowScopesRequired(origin *Origin) error { + return newRequiredField("oAuthFlow.scopes", + &OAuthFlowScopesRequired{ValidationError{Message: "field 'scopes' is missing"}}, origin) +} + +func newOAuthFlowAuthorizationURLRequired(origin *Origin) error { + return newRequiredField("oAuthFlow.authorizationUrl", + &OAuthFlowAuthorizationURLRequired{ValidationError{Message: "field 'authorizationUrl' is empty or missing"}}, origin) +} + +func newOAuthFlowTokenURLRequired(origin *Origin) error { + return newRequiredField("oAuthFlow.tokenUrl", + &OAuthFlowTokenURLRequired{ValidationError{Message: "field 'tokenUrl' is empty or missing"}}, origin) +} + +// newMutuallyExclusiveFields wraps leaf in a *MutuallyExclusiveFieldsError +// carrying the two field names the spec forbids setting together. +func newMutuallyExclusiveFields(field1, field2 string, leaf error, origin *Origin) error { + return &MutuallyExclusiveFieldsError{ + Field1: field1, + Field2: field2, + Cause: leaf, + Origin: origin, + } +} + +func newExampleValueExternalValueExclusive(origin *Origin) error { + const msg = "value and externalValue are mutually exclusive" + return newMutuallyExclusiveFields("value", "externalValue", + &ExampleValueExternalValueExclusive{ValidationError{Message: msg}}, origin) +} + +func newMediaTypeExampleExamplesExclusive(origin *Origin) error { + const msg = "example and examples are mutually exclusive" + return newMutuallyExclusiveFields("example", "examples", + &MediaTypeExampleExamplesExclusive{ValidationError{Message: msg}}, origin) +} + +func newLicenseURLIdentifierExclusive(origin *Origin) error { + const msg = "license must not specify both 'url' and 'identifier'" + return newMutuallyExclusiveFields("url", "identifier", + &LicenseURLIdentifierExclusive{ValidationError{Message: msg}}, origin) +} + +func newLinkOperationIDRefExclusive(operationID, operationRef string, origin *Origin) error { + msg := fmt.Sprintf("operationId %q and operationRef %q are mutually exclusive", operationID, operationRef) + return newMutuallyExclusiveFields("operationId", "operationRef", + &LinkOperationIDRefExclusive{ValidationError{Message: msg}}, origin) +} + +func newSchemaReadOnlyWriteOnlyExclusive(origin *Origin) error { + const msg = "a property MUST NOT be marked as both readOnly and writeOnly being true" + return newMutuallyExclusiveFields("readOnly", "writeOnly", + &SchemaReadOnlyWriteOnlyExclusive{ValidationError{Message: msg}}, origin) +} + +// newForbiddenField wraps leaf in a *ForbiddenFieldError carrying the +// name of the field that the spec forbids in the current context. +func newForbiddenField(field string, leaf error, origin *Origin) error { + return &ForbiddenFieldError{Field: field, Cause: leaf, Origin: origin} +} + +func newHeaderNameForbidden(origin *Origin) error { + const msg = "header 'name' MUST NOT be specified, it is given in the corresponding headers map" + return newForbiddenField("name", + &HeaderNameForbidden{ValidationError{Message: msg}}, origin) +} + +func newHeaderInForbidden(origin *Origin) error { + const msg = "header 'in' MUST NOT be specified, it is implicitly in header" + return newForbiddenField("in", + &HeaderInForbidden{ValidationError{Message: msg}}, origin) +} + +func newOAuthFlowAuthorizationURLForbidden(origin *Origin) error { + const msg = "field 'authorizationUrl' should not be set" + return newForbiddenField("authorizationUrl", + &OAuthFlowAuthorizationURLForbidden{ValidationError{Message: msg}}, origin) +} + +func newOAuthFlowTokenURLForbidden(origin *Origin) error { + const msg = "field 'tokenUrl' should not be set" + return newForbiddenField("tokenUrl", + &OAuthFlowTokenURLForbidden{ValidationError{Message: msg}}, origin) +} + +func newParameterNameRequired(origin *Origin) error { + return newRequiredField("parameter.name", + &ParameterNameRequired{ValidationError{Message: "parameter name can't be blank"}}, origin) +} + +func newResponsesNonEmptyRequired(origin *Origin) error { + const msg = "the responses object MUST contain at least one response code" + return newRequiredField("responses", + &ResponsesNonEmptyRequired{ValidationError{Message: msg}}, origin) +} + +func newAPIKeySecuritySchemeNameRequired(origin *Origin) error { + const msg = "security scheme of type 'apiKey' should have 'name'" + return newRequiredField("securityScheme.name", + &APIKeySecuritySchemeNameRequired{ValidationError{Message: msg}}, origin) +} + +// newSchemaValueError wraps the result of schema.VisitJSON in a +// *SchemaValueError cluster, identifying which schema sub-field +// (example, default, ...) carried the offending value. cause is +// either a *SchemaError or a MultiError of them. +func newSchemaValueError(valueKind string, cause error, origin *Origin) error { + return &SchemaValueError{ValueKind: valueKind, Cause: cause, Origin: origin} +} + +// exampleValueOrigin returns an Origin pinned to the example's `value:` +// field, used when wrapping a plural Examples entry's validation failure. +// Falls back to the example's struct origin, then the parent fallback +// origin (parameter or media type), so consumers always have something +// useful to deep-link to. +func exampleValueOrigin(ex *Example, fallback *Origin) *Origin { + if ex == nil || ex.Origin == nil { + return fallback + } + if loc, ok := ex.Origin.Fields["value"]; ok { + return &Origin{Key: &loc} + } + return ex.Origin +} + +// newFieldVersionMismatch wraps leaf in a FieldVersionMismatchError for the +// given field at minimum version 3.1. Used by per-call-site constructors +// (newInfoSummaryFieldFor31Plus, etc.) and by the dispatch helper +// newFieldFor31Plus that schema.go's reject closure goes through. +func newFieldVersionMismatch(field string, leaf error, origin *Origin) error { + return &FieldVersionMismatchError{ + Field: field, + MinVersion: "3.1", + Cause: leaf, + Origin: origin, + } +} + +// Per-call-site constructors for the four non-schema FieldFor31Plus sites +// (info.summary, license.identifier, doc.webhooks, doc.jsonSchemaDialect). +// The schema fields go through fieldFor31PlusLeaves below because they're +// dispatched from a runtime-parameterised closure in schema.go's reject. + +func newInfoSummaryFieldFor31Plus(origin *Origin) error { + const msg = "field summary is for OpenAPI >=3.1" + return newFieldVersionMismatch("summary", + &InfoSummaryFieldFor31Plus{ValidationError{Message: msg}}, origin) +} + +func newLicenseIdentifierFieldFor31Plus(origin *Origin) error { + const msg = "field identifier is for OpenAPI >=3.1" + return newFieldVersionMismatch("identifier", + &LicenseIdentifierFieldFor31Plus{ValidationError{Message: msg}}, origin) +} + +func newWebhooksFieldFor31Plus(origin *Origin) error { + const msg = "field webhooks is for OpenAPI >=3.1" + return newFieldVersionMismatch("webhooks", + &WebhooksFieldFor31Plus{ValidationError{Message: msg}}, origin) +} + +func newJSONSchemaDialectFieldFor31Plus(origin *Origin) error { + const msg = "field jsonschemadialect is for OpenAPI >=3.1" + return newFieldVersionMismatch("jsonschemadialect", + &JSONSchemaDialectFieldFor31Plus{ValidationError{Message: msg}}, origin) +} + +// fieldFor31PlusLeaves maps field names (as passed to errFieldFor31Plus) +// to their typed leaf constructors. Only schema-keyword fields are in +// the table — those are dispatched at runtime from schema.go's reject +// closure. The four non-schema fields (summary, identifier, webhooks, +// jsonschemadialect) have direct constructors above. Any field not in +// the map falls back to a bare *ValidationError, so callers still get +// the cluster + base layers — only the per-leaf type is missing. +var fieldFor31PlusLeaves = map[string]func(msg string) error{ + "const": func(m string) error { return &ConstFieldFor31Plus{ValidationError{Message: m}} }, + "examples": func(m string) error { return &ExamplesFieldFor31Plus{ValidationError{Message: m}} }, + "prefixItems": func(m string) error { return &PrefixItemsFieldFor31Plus{ValidationError{Message: m}} }, + "contains": func(m string) error { return &ContainsFieldFor31Plus{ValidationError{Message: m}} }, + "minContains": func(m string) error { return &MinContainsFieldFor31Plus{ValidationError{Message: m}} }, + "maxContains": func(m string) error { return &MaxContainsFieldFor31Plus{ValidationError{Message: m}} }, + "patternProperties": func(m string) error { return &PatternPropertiesFieldFor31Plus{ValidationError{Message: m}} }, + "dependentSchemas": func(m string) error { return &DependentSchemasFieldFor31Plus{ValidationError{Message: m}} }, + "propertyNames": func(m string) error { return &PropertyNamesFieldFor31Plus{ValidationError{Message: m}} }, + "unevaluatedItems": func(m string) error { return &UnevaluatedItemsFieldFor31Plus{ValidationError{Message: m}} }, + "unevaluatedProperties": func(m string) error { return &UnevaluatedPropertiesFieldFor31Plus{ValidationError{Message: m}} }, + "if": func(m string) error { return &IfFieldFor31Plus{ValidationError{Message: m}} }, + "then": func(m string) error { return &ThenFieldFor31Plus{ValidationError{Message: m}} }, + "else": func(m string) error { return &ElseFieldFor31Plus{ValidationError{Message: m}} }, + "dependentRequired": func(m string) error { return &DependentRequiredFieldFor31Plus{ValidationError{Message: m}} }, + "contentEncoding": func(m string) error { return &ContentEncodingFieldFor31Plus{ValidationError{Message: m}} }, + "contentMediaType": func(m string) error { return &ContentMediaTypeFieldFor31Plus{ValidationError{Message: m}} }, + "contentSchema": func(m string) error { return &ContentSchemaFieldFor31Plus{ValidationError{Message: m}} }, + "$defs": func(m string) error { return &DefsFieldFor31Plus{ValidationError{Message: m}} }, + "$schema": func(m string) error { return &SchemaFieldFor31Plus{ValidationError{Message: m}} }, + "$comment": func(m string) error { return &CommentFieldFor31Plus{ValidationError{Message: m}} }, + "$id": func(m string) error { return &IDFieldFor31Plus{ValidationError{Message: m}} }, + "$anchor": func(m string) error { return &AnchorFieldFor31Plus{ValidationError{Message: m}} }, + "$dynamicAnchor": func(m string) error { return &DynamicAnchorFieldFor31Plus{ValidationError{Message: m}} }, + "$dynamicRef": func(m string) error { return &DynamicRefFieldFor31Plus{ValidationError{Message: m}} }, +} + +// newFieldFor31Plus dispatches errFieldFor31Plus's per-field message +// to the right typed leaf and wraps it in a FieldVersionMismatchError. +// Fields not in fieldFor31PlusLeaves fall back to a bare +// *ValidationError so the caller still gets a stable Message and the +// cluster + base layers; only the per-leaf type is missing. +// +// Reached only from schema.go's reject closure with a runtime field +// name; the four non-schema sites use direct constructors instead. +func newFieldFor31Plus(field string, origin *Origin) error { + msg := "field " + field + " is for OpenAPI >=3.1" + var leaf error + if ctor, ok := fieldFor31PlusLeaves[field]; ok { + leaf = ctor(msg) + } else { + leaf = &ValidationError{Message: msg} + } + return newFieldVersionMismatch(field, leaf, origin) +} + +func newPathParameterRequired(param string, origin *Origin) error { + return &PathParameterRequiredError{Param: param, Origin: origin} +} + +func newDuplicateOperationID(endpoint1, endpoint2, operationID string, origin *Origin) error { + return &DuplicateOperationIDError{ + Endpoint1: endpoint1, + Endpoint2: endpoint2, + OperationID: operationID, + Origin: origin, + } +} + +func newExtraSiblingFields(fields []string, origin *Origin) error { + return &ExtraSiblingFieldsError{Fields: fields, Origin: origin} +} + +func newSchemaTypeError(typ string, origin *Origin) error { + return &SchemaTypeError{Type: typ, Origin: origin} +} + +func newInvalidParameterIn(value string, origin *Origin) error { + return &InvalidParameterInError{Value: value, Origin: origin} +} + +func newSchemaPatternRegexError(pattern string, cause error, origin *Origin) error { + return &SchemaPatternRegexError{Pattern: pattern, Cause: cause, Origin: origin} +} + +func newInvalidSecuritySchemeType(typ string, origin *Origin) error { + return &InvalidSecuritySchemeTypeError{Type: typ, Origin: origin} +} + +func newInvalidHTTPScheme(scheme string, origin *Origin) error { + return &InvalidHTTPSchemeError{Scheme: scheme, Origin: origin} +} + +func newUnresolvedRef(ref string, origin *Origin) error { + return &UnresolvedRefError{Ref: ref, Origin: origin} +} + +func newAPIKeyInInvalid(value string, origin *Origin) error { + return &APIKeyInInvalidError{Value: value, Origin: origin} +} + +func newOpenIDConnectURLRequired(schemeName string, origin *Origin) error { + return newRequiredField("openIdConnectUrl", + &OpenIDConnectURLRequired{ValidationError{Message: fmt.Sprintf("no OIDC URL found for openIdConnect security scheme %q", schemeName)}}, origin) +} + +func newSecuritySchemeFlowsRequired(schemeType string, origin *Origin) error { + return newRequiredField("flows", + &SecuritySchemeFlowsRequired{ValidationError{Message: fmt.Sprintf("security scheme of type %q should have 'flows'", schemeType)}}, origin) +} + +func newSecuritySchemeInForbidden(schemeType string, origin *Origin) error { + return newForbiddenField("in", + &SecuritySchemeInForbidden{ValidationError{Message: fmt.Sprintf("security scheme of type %q can't have 'in'", schemeType)}}, origin) +} + +func newSecuritySchemeNameForbidden(schemeType string, origin *Origin) error { + return newForbiddenField("name", + &SecuritySchemeNameForbidden{ValidationError{Message: fmt.Sprintf("security scheme of type %q can't have 'name'", schemeType)}}, origin) +} + +func newSecuritySchemeBearerFormatForbidden(schemeType string, origin *Origin) error { + return newForbiddenField("bearerFormat", + &SecuritySchemeBearerFormatForbidden{ValidationError{Message: fmt.Sprintf("security scheme of type %q can't have 'bearerFormat'", schemeType)}}, origin) +} + +func newSecuritySchemeFlowsForbidden(schemeType string, origin *Origin) error { + return newForbiddenField("flows", + &SecuritySchemeFlowsForbidden{ValidationError{Message: fmt.Sprintf("security scheme of type %q can't have 'flows'", schemeType)}}, origin) +} + +func newPathMustStartWithSlash(path string, origin *Origin) error { + return &PathMustStartWithSlashError{Path: path, Origin: origin} +} + +func newConflictingPaths(path1, path2 string, origin *Origin) error { + return &ConflictingPathsError{Path1: path1, Path2: path2, Origin: origin} +} + +func newDuplicateParameter(in, name string, origin *Origin) error { + return &DuplicateParameterError{In: in, Name: name, Origin: origin} +} + +func newInvalidSerializationMethod(subject, style string, explode bool, origin *Origin) error { + return &InvalidSerializationMethodError{Subject: subject, Style: style, Explode: explode, Origin: origin} +} + +func newParameterExampleAndExamplesExclusive(parameterName string, origin *Origin) error { + return newMutuallyExclusiveFields("example", "examples", + &ParameterExampleAndExamplesExclusive{ValidationError{Message: fmt.Sprintf("parameter %q example and examples are mutually exclusive", parameterName)}}, origin) +} + +func newServerVariableDefaultRequired(serverData string, origin *Origin) error { + return newRequiredField("default", + &ServerVariableDefaultRequired{ValidationError{Message: fmt.Sprintf("field default is required in %s", serverData)}}, origin) +} diff --git a/openapi3/validation_error_astype_test.go b/openapi3/validation_error_astype_test.go new file mode 100644 index 000000000..f51a44207 --- /dev/null +++ b/openapi3/validation_error_astype_test.go @@ -0,0 +1,41 @@ +//go:build go1.26 + +// Build-tagged go1.26 because errors.AsType was introduced in Go 1.26. +// The file compiles only on toolchains where the function exists; on +// older toolchains it's silently excluded. + +package openapi3_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +// errors.AsType is the generic complement to errors.As — same chain +// walk, but returns the typed value directly instead of populating an +// out-parameter. Demonstrating it works with the validation-error +// hierarchy at all three layers (cluster, leaf, base) confirms the +// design is idiomatic under Go 1.26's improved errors API. +func TestValidationError_AsTypeWalksAllLayers(t *testing.T) { + err := (&openapi3.Info{Title: "x"}).Validate(context.Background()) + + // Cluster. + rfe, ok := errors.AsType[*openapi3.RequiredFieldError](err) + require.True(t, ok) + require.Equal(t, "info.version", rfe.Field) + + // Leaf — reached via errors.Unwrap-walking, same as errors.As. + _, ok = errors.AsType[*openapi3.InfoVersionRequired](err) + require.True(t, ok) + + // Base — reached via the leaf's As method exposing the embedded + // *ValidationError, same as errors.As. + ve, ok := errors.AsType[*openapi3.ValidationError](err) + require.True(t, ok) + require.Equal(t, "value of version must be a non-empty string", ve.Message) +} diff --git a/openapi3/validation_error_context.go b/openapi3/validation_error_context.go new file mode 100644 index 000000000..3172230a7 --- /dev/null +++ b/openapi3/validation_error_context.go @@ -0,0 +1,252 @@ +// Context wrappers are the 4th category of typed validation errors, +// alongside the Base / Cluster / Leaf model documented at the top of +// validation_error.go. +// +// A context wrapper carries scope (which section, path, operation, +// component, parameter, OAuth flow, ...) around an inner error chain. +// It does NOT itself report a validation failure — the actual error +// lives in Cause and is reachable via errors.Unwrap / errors.As. +// +// Use a context wrapper to extract "where the error happened" without +// parsing the rendered message. Combine with errors.As against the +// inner cluster or leaf to also extract "what category" and "exactly +// which case." A canonical error chain looks like: +// +// ComponentValidationError{Section, Name} // context wrapper: WHERE +// -> RequiredFieldError{Field} // cluster: WHAT CATEGORY +// -> SomeFieldRequired{Message} // leaf: EXACTLY WHICH CASE +// +// Two scopes of context wrapper live in this file: +// +// - Document-wide wrappers — SectionValidationError, +// PathValidationError, OperationValidationError. Cover entire +// top-level scopes of the document. +// - Narrow-scope wrappers — ComponentValidationError, +// ExternalDocsURLValidationError, HeaderFieldValidationError, +// MediaTypeExampleValidationError, WebhookValidationError, +// ParameterFieldValidationError, ParameterExampleValidationError, +// SecuritySchemeFlowValidationError, OAuthFlowValidationError, +// OAuthFlowFieldValidationError. Cover a specific validation +// surface inside a section. +// +// Both scopes follow the same shape: +// +// - One or more discriminator fields naming the scope (Section, +// Path, ParameterName + Field, etc.). +// - A Cause error that holds the wrapped inner error. +// - Unwrap() returns Cause so errors.As walks transparently to the +// inner cluster or leaf. +// +// Error() formats vary per wrapper: each preserves the original +// fmt.Errorf-with-%w message format byte-for-byte for backward +// compatibility, so existing string-matching consumers see identical +// output. There is no canonical ": " format across +// wrappers — read each type's Error() if the exact string matters. + +package openapi3 + +import "fmt" + +// SectionValidationError wraps an error originating inside one of the +// top-level OpenAPI document sections (info, paths, components, +// security, servers, tags, externalDocs, webhooks, jsonSchemaDialect). +// Section is the OpenAPI field name as it appears in the document +// root. +// +// Use errors.As(err, &sve) to extract the section context from a +// validation error chain without parsing the rendered message. +type SectionValidationError struct { + Section string + Cause error +} + +func (e *SectionValidationError) Error() string { + return fmt.Sprintf("invalid %s: %v", e.Section, e.Cause) +} + +func (e *SectionValidationError) Unwrap() error { return e.Cause } + +// PathValidationError wraps an error originating inside a specific path. +// Path is the path template as it appears in the document (e.g. +// "/users/{id}"). +// +// Use errors.As(err, &pve) to extract the path from a validation +// error chain without parsing the rendered message. +type PathValidationError struct { + Path string + Cause error +} + +func (e *PathValidationError) Error() string { + return fmt.Sprintf("invalid path %s: %v", e.Path, e.Cause) +} + +func (e *PathValidationError) Unwrap() error { return e.Cause } + +// OperationValidationError wraps an error originating inside a specific +// HTTP-method operation under a path. Method is the uppercase method +// (GET, POST, etc.). +// +// Use errors.As(err, &ove) to extract the method from a validation +// error chain without parsing the rendered message. +type OperationValidationError struct { + Method string + Cause error +} + +func (e *OperationValidationError) Error() string { + return fmt.Sprintf("invalid operation %s: %v", e.Method, e.Cause) +} + +func (e *OperationValidationError) Unwrap() error { return e.Cause } + +// Below: narrow-scope context wrappers covering specific validation +// surfaces inside a section. See the file-level header above for the +// full inventory and how these relate to the document-wide wrappers +// above. + +// ComponentValidationError wraps validation errors inside the +// Components container, carrying which sub-section (Schemas, +// Parameters, etc.) and which component name failed. +type ComponentValidationError struct { + // Section is the lowercase singular form of the component bucket + // ("schema", "parameter", "header", "request body", "response", + // "security scheme", "example", "link", "callback"). + Section string + // Name is the component map key. + Name string + Cause error +} + +func (e *ComponentValidationError) Error() string { + return fmt.Sprintf("%s %q: %v", e.Section, e.Name, e.Cause) +} + +func (e *ComponentValidationError) Unwrap() error { return e.Cause } + +// ExternalDocsURLValidationError wraps the URL parse failure on an +// ExternalDocs object. +type ExternalDocsURLValidationError struct { + Cause error +} + +func (e *ExternalDocsURLValidationError) Error() string { + return fmt.Sprintf("url is incorrect: %v", e.Cause) +} + +func (e *ExternalDocsURLValidationError) Unwrap() error { return e.Cause } + +// HeaderFieldValidationError wraps validation errors on a Header's +// `schema` or `content` sub-objects. Field discriminates the two. +type HeaderFieldValidationError struct { + // Field is "schema" or "content". + Field string + Cause error +} + +func (e *HeaderFieldValidationError) Error() string { + return fmt.Sprintf("header %s is invalid: %v", e.Field, e.Cause) +} + +func (e *HeaderFieldValidationError) Unwrap() error { return e.Cause } + +// MediaTypeExampleValidationError wraps validation errors on a named +// example inside a MediaType.examples map. +type MediaTypeExampleValidationError struct { + // ExampleName is the example map key. + ExampleName string + Cause error +} + +func (e *MediaTypeExampleValidationError) Error() string { + return fmt.Sprintf("example %s: %v", e.ExampleName, e.Cause) +} + +func (e *MediaTypeExampleValidationError) Unwrap() error { return e.Cause } + +// WebhookValidationError wraps validation errors on a named webhook +// at the document root (OpenAPI 3.1+). +type WebhookValidationError struct { + // Name is the webhook map key. + Name string + Cause error +} + +func (e *WebhookValidationError) Error() string { + return fmt.Sprintf("webhook %q: %v", e.Name, e.Cause) +} + +func (e *WebhookValidationError) Unwrap() error { return e.Cause } + +// ParameterFieldValidationError wraps validation errors on a +// parameter's `schema` or `content` sub-objects. Field discriminates. +type ParameterFieldValidationError struct { + // ParameterName is the parameter's `name:` value. + ParameterName string + // Field is "schema" or "content". + Field string + Cause error +} + +func (e *ParameterFieldValidationError) Error() string { + return fmt.Sprintf("parameter %q %s is invalid: %v", e.ParameterName, e.Field, e.Cause) +} + +func (e *ParameterFieldValidationError) Unwrap() error { return e.Cause } + +// ParameterExampleValidationError wraps validation errors on a named +// example inside a parameter's examples map. +type ParameterExampleValidationError struct { + // ExampleName is the example map key. + ExampleName string + Cause error +} + +func (e *ParameterExampleValidationError) Error() string { + return fmt.Sprintf("%s: %v", e.ExampleName, e.Cause) +} + +func (e *ParameterExampleValidationError) Unwrap() error { return e.Cause } + +// SecuritySchemeFlowValidationError wraps validation errors on the +// outer flows object of an oauth2 security scheme. +type SecuritySchemeFlowValidationError struct { + Cause error +} + +func (e *SecuritySchemeFlowValidationError) Error() string { + return fmt.Sprintf("security scheme 'flow' is invalid: %v", e.Cause) +} + +func (e *SecuritySchemeFlowValidationError) Unwrap() error { return e.Cause } + +// OAuthFlowValidationError wraps validation errors on a specific +// OAuth flow inside OAuthFlows. +type OAuthFlowValidationError struct { + // FlowKind is one of "implicit", "password", "clientCredentials", + // "authorizationCode". + FlowKind string + Cause error +} + +func (e *OAuthFlowValidationError) Error() string { + return fmt.Sprintf("the OAuth flow %q is invalid: %v", e.FlowKind, e.Cause) +} + +func (e *OAuthFlowValidationError) Unwrap() error { return e.Cause } + +// OAuthFlowFieldValidationError wraps validation errors on a specific +// field inside an OAuthFlow object. Field discriminates which URL +// field failed. +type OAuthFlowFieldValidationError struct { + // Field is the offending field name ("refreshUrl" is the only + // site today; future URL fields can reuse the same wrapper). + Field string + Cause error +} + +func (e *OAuthFlowFieldValidationError) Error() string { + return fmt.Sprintf("field %q is invalid: %v", e.Field, e.Cause) +} + +func (e *OAuthFlowFieldValidationError) Unwrap() error { return e.Cause } diff --git a/openapi3/validation_error_context_test.go b/openapi3/validation_error_context_test.go new file mode 100644 index 000000000..cced3f14c --- /dev/null +++ b/openapi3/validation_error_context_test.go @@ -0,0 +1,98 @@ +package openapi3 + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +// Error() text format: "invalid
: " — byte-identical to +// the fmt.Errorf wrapper this type replaced. +func TestSectionValidationError_Error(t *testing.T) { + inner := errors.New("value of version must be a non-empty string") + e := &SectionValidationError{Section: "info", Cause: inner} + require.Equal(t, "invalid info: value of version must be a non-empty string", e.Error()) +} + +// Unwrap returns the inner error so errors.Is / errors.As walk the chain. +func TestSectionValidationError_Unwrap(t *testing.T) { + sentinel := errors.New("sentinel") + e := &SectionValidationError{Section: "components", Cause: sentinel} + require.True(t, errors.Is(e, sentinel)) +} + +func TestPathValidationError_Error(t *testing.T) { + inner := errors.New("bad path item") + e := &PathValidationError{Path: "/users/{id}", Cause: inner} + require.Equal(t, "invalid path /users/{id}: bad path item", e.Error()) +} + +func TestPathValidationError_Unwrap(t *testing.T) { + sentinel := errors.New("sentinel") + e := &PathValidationError{Path: "/x", Cause: sentinel} + require.True(t, errors.Is(e, sentinel)) +} + +func TestOperationValidationError_Error(t *testing.T) { + inner := errors.New("bad operation") + e := &OperationValidationError{Method: "GET", Cause: inner} + require.Equal(t, "invalid operation GET: bad operation", e.Error()) +} + +func TestOperationValidationError_Unwrap(t *testing.T) { + sentinel := errors.New("sentinel") + e := &OperationValidationError{Method: "POST", Cause: sentinel} + require.True(t, errors.Is(e, sentinel)) +} + +// Three-layer chain (section + path + operation) is the typical shape +// for a schema-deep validation error inside paths. errors.As against +// each cluster pulls the corresponding context independently of the +// others; nothing depends on the order in which the layers were +// wrapped. +func TestSectionPathOperationChain_AsExtraction(t *testing.T) { + leaf := errors.New("field const is for OpenAPI >=3.1") + chain := &SectionValidationError{ + Section: "paths", + Cause: &PathValidationError{ + Path: "/thing", + Cause: &OperationValidationError{ + Method: "GET", + Cause: leaf, + }, + }, + } + + var sve *SectionValidationError + require.True(t, errors.As(chain, &sve)) + require.Equal(t, "paths", sve.Section) + + var pve *PathValidationError + require.True(t, errors.As(chain, &pve)) + require.Equal(t, "/thing", pve.Path) + + var ove *OperationValidationError + require.True(t, errors.As(chain, &ove)) + require.Equal(t, "GET", ove.Method) + + // And the leaf is still reachable via errors.Is. + require.True(t, errors.Is(chain, leaf)) + + // And the rendered string concatenates the layers exactly as the + // previous fmt.Errorf chain did. + require.Equal(t, + "invalid paths: invalid path /thing: invalid operation GET: field const is for OpenAPI >=3.1", + chain.Error(), + ) +} + +// Mixed: a SectionValidationError wrapping an arbitrary non-typed error +// still renders correctly. Guards against the typed wrapper changing +// behaviour when the underlying error is not one of our cluster types. +func TestSectionValidationError_WrappingArbitrary(t *testing.T) { + inner := fmt.Errorf("third-party error: %w", errors.New("x")) + e := &SectionValidationError{Section: "webhooks", Cause: inner} + require.Equal(t, "invalid webhooks: third-party error: x", e.Error()) +} diff --git a/openapi3/validation_error_test.go b/openapi3/validation_error_test.go new file mode 100644 index 000000000..56bafef8a --- /dev/null +++ b/openapi3/validation_error_test.go @@ -0,0 +1,2076 @@ +package openapi3_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Existing string-comparison consumers must keep working unchanged. +// The leaf-typed sites in Info.Validate must produce the exact same +// Error() strings they used to produce as plain errors.New(...) values. +func TestValidationError_BackwardCompatibleErrorString(t *testing.T) { + missingVersion := &openapi3.Info{Title: "x"} + require.EqualError(t, missingVersion.Validate(context.Background()), + "value of version must be a non-empty string") + + missingTitle := &openapi3.Info{Version: "1.0.0"} + require.EqualError(t, missingTitle.Validate(context.Background()), + "value of title must be a non-empty string") +} + +// Three layers of granularity, all reachable from the same returned +// error: base ValidationError, cluster RequiredFieldError, and the +// per-site leaf type (e.g. *InfoVersionRequired). +func TestValidationError_ThreeLayers_RequiredField(t *testing.T) { + err := (&openapi3.Info{Title: "x"}).Validate(context.Background()) + + // Layer 1: cluster — carries field-level metadata. + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "info.version", rfe.Field) + + // Layer 2: leaf — exact identification of which site fired. + var ivr *openapi3.InfoVersionRequired + require.True(t, errors.As(err, &ivr)) + + // Layer 3: base — catchall for "this is a validation error". + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + require.Equal(t, "value of version must be a non-empty string", ve.Message) +} + +func TestValidationError_LeafDifferentiation(t *testing.T) { + verErr := (&openapi3.Info{Title: "x"}).Validate(context.Background()) + titleErr := (&openapi3.Info{Version: "1.0.0"}).Validate(context.Background()) + + // Title's leaf type does NOT match the version's leaf type, even + // though both flow through the same RequiredFieldError cluster. + var ivr *openapi3.InfoVersionRequired + require.True(t, errors.As(verErr, &ivr)) + require.False(t, errors.As(titleErr, &ivr)) + + var itr *openapi3.InfoTitleRequired + require.True(t, errors.As(titleErr, &itr)) + require.False(t, errors.As(verErr, &itr)) + + // Both still share the cluster type. + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(verErr, &rfe)) + require.Equal(t, "info.version", rfe.Field) + require.True(t, errors.As(titleErr, &rfe)) + require.Equal(t, "info.title", rfe.Field) +} + +// FieldVersionMismatchError cluster, exercised by the existing +// errFieldFor31Plus helper (license.identifier in a 3.0 doc). +func TestValidationError_ThreeLayers_FieldVersionMismatch(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "x", + Version: "1.0.0", + License: &openapi3.License{ + Name: "MIT", + Identifier: "MIT", // 3.1+ only + }, + }, + Paths: openapi3.NewPaths(), + } + err := doc.Validate(context.Background()) + require.Error(t, err) + + var fvm *openapi3.FieldVersionMismatchError + require.True(t, errors.As(err, &fvm)) + require.Equal(t, "identifier", fvm.Field) + require.Equal(t, "3.1", fvm.MinVersion) + + var lif *openapi3.LicenseIdentifierFieldFor31Plus + require.True(t, errors.As(err, &lif)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + require.Contains(t, ve.Message, "identifier") +} + +// Untyped fields (those not yet assigned a leaf in newFieldFor31Plus's +// switch) still flow through the cluster + base, so callers retain +// the same discrimination layers as their typed cousins. Only the +// per-leaf type isn't there to assert against. +func TestValidationError_FieldVersionMismatch_UntypedFallback(t *testing.T) { + // "webhooks" is an existing untyped 3.1+-only field — see + // openapi3.go's errFieldFor31Plus("webhooks") site. + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{}, + } + err := doc.Validate(context.Background()) + require.Error(t, err) + + var fvm *openapi3.FieldVersionMismatchError + require.True(t, errors.As(err, &fvm)) + require.Equal(t, "webhooks", fvm.Field) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) +} + +// Spot-check the rest of the converted call sites: each required field +// produces its own leaf type plus the shared cluster, and each typed +// 3.1+-only schema field produces its own leaf type plus the shared +// cluster. +func TestValidationError_AllRequiredFieldLeaves(t *testing.T) { + type tc struct { + name string + doc *openapi3.T + leafCheck func(t *testing.T, err error) + field string + message string + } + cases := []tc{ + { + name: "openapi version required", + doc: &openapi3.T{}, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.OpenAPIVersionRequired + require.True(t, errors.As(err, &l)) + }, + field: "openapi", + message: "value of openapi must be a non-empty string", + }, + { + name: "license name required", + doc: &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "x", Version: "1.0.0", + License: &openapi3.License{}, + }, + Paths: openapi3.NewPaths(), + }, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.LicenseNameRequired + require.True(t, errors.As(err, &l)) + }, + field: "license.name", + message: "value of license name must be a non-empty string", + }, + { + name: "server url required", + doc: &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + Servers: openapi3.Servers{&openapi3.Server{}}, + }, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.ServerURLRequired + require.True(t, errors.As(err, &l)) + }, + field: "server.url", + message: "value of url must be a non-empty string", + }, + { + name: "responses non-empty required", + doc: &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(openapi3.WithPath("/p", &openapi3.PathItem{ + Get: &openapi3.Operation{Responses: openapi3.NewResponses()}, + })), + }, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.ResponsesNonEmptyRequired + require.True(t, errors.As(err, &l)) + }, + field: "responses", + message: "the responses object MUST contain at least one response code", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + err := c.doc.Validate(context.Background()) + require.Error(t, err) + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, c.field, rfe.Field) + + c.leafCheck(t, err) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + require.Equal(t, c.message, ve.Message) + }) + } +} + +// Spot-check a couple of the schema-field leaves that flow through +// errFieldFor31Plus (used by schema.go's reject() helper). Full +// per-field coverage is left to the package's existing schema_test.go. +func TestValidationError_SchemaFieldFor31PlusLeaves(t *testing.T) { + type tc struct { + name string + schema *openapi3.Schema + leafCheck func(t *testing.T, err error) + field string + } + cases := []tc{ + { + name: "const", + schema: &openapi3.Schema{Const: "x"}, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.ConstFieldFor31Plus + require.True(t, errors.As(err, &l)) + }, + field: "const", + }, + { + name: "patternProperties", + schema: &openapi3.Schema{ + PatternProperties: map[string]*openapi3.SchemaRef{"foo": {Value: &openapi3.Schema{}}}, + }, + leafCheck: func(t *testing.T, err error) { + var l *openapi3.PatternPropertiesFieldFor31Plus + require.True(t, errors.As(err, &l)) + }, + field: "patternProperties", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + err := c.schema.Validate(context.Background()) + require.Error(t, err) + + var fvm *openapi3.FieldVersionMismatchError + require.True(t, errors.As(err, &fvm)) + require.Equal(t, c.field, fvm.Field) + require.Equal(t, "3.1", fvm.MinVersion) + + c.leafCheck(t, err) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + } +} + +// Cluster types wrap their leaves through standard Go error wrapping, +// so errors.Unwrap walks from the cluster to the leaf in a single step. +// Pinning this directly (rather than only via errors.As) demonstrates +// that the chain follows the conventional Unwrap contract — useful for +// any consumer that walks the error tree by hand instead of asking for +// a specific type. +func TestValidationError_UnwrapWalksClusterToLeaf(t *testing.T) { + // RequiredFieldError cluster wrapping an InfoVersionRequired leaf. + verErr := (&openapi3.Info{Title: "x"}).Validate(context.Background()) + + // The returned error IS the cluster, not the leaf. + rfe, ok := verErr.(*openapi3.RequiredFieldError) + require.True(t, ok, "validator returns the cluster type") + require.Equal(t, "info.version", rfe.Field) + + // errors.Unwrap takes us to the leaf in one step. + leaf := errors.Unwrap(verErr) + require.NotNil(t, leaf) + _, isLeaf := leaf.(*openapi3.InfoVersionRequired) + require.True(t, isLeaf, "Unwrap reaches the leaf type") + + // The leaf is terminal — nothing further to unwrap. + require.Nil(t, errors.Unwrap(leaf), "leaf has no inner error") + + // Same shape for the FieldVersionMismatchError cluster wrapping a + // LicenseIdentifierFieldFor31Plus leaf. + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{ + Title: "x", Version: "1.0.0", + License: &openapi3.License{Name: "MIT", Identifier: "MIT"}, + }, + Paths: openapi3.NewPaths(), + } + docErr := doc.Validate(context.Background()) + + // doc.Validate wraps the License error in MultiError variants. Walk + // to the FieldVersionMismatchError cluster via errors.As (since + // MultiError sits between). + var fvm *openapi3.FieldVersionMismatchError + require.True(t, errors.As(docErr, &fvm)) + require.Equal(t, "identifier", fvm.Field) + + // From the cluster, Unwrap reaches the leaf directly. + licenseLeaf := errors.Unwrap(fvm) + require.NotNil(t, licenseLeaf) + _, isLicenseLeaf := licenseLeaf.(*openapi3.LicenseIdentifierFieldFor31Plus) + require.True(t, isLicenseLeaf, "Unwrap reaches the leaf type") + require.Nil(t, errors.Unwrap(licenseLeaf), "leaf has no inner error") +} + +// Origin is populated on cluster types when the document was loaded +// with Loader.IncludeOrigin = true. The cluster carries the offending +// element's Origin (info, license, server, schema, ...) so consumers +// can attach file/line/column to a finding without re-walking the doc. +func TestValidationError_OriginPopulatedOnLoaderTracking(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: + title: x + version: "" +paths: {} +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(verr, &rfe)) + require.Equal(t, "info.version", rfe.Field) + require.NotNil(t, rfe.Origin, "cluster should carry info.Origin when loader tracks origins") + require.NotNil(t, rfe.Origin.Key, "Origin.Key set by the loader") + // File is empty for LoadFromData (no path associated); LoadFromFile + // would populate it. Line/Column are populated either way. + require.Greater(t, rfe.Origin.Key.Line, 0) +} + +// Without IncludeOrigin, Origin is nil — we don't fabricate location +// info that wasn't tracked. +func TestValidationError_OriginNilWithoutLoaderTracking(t *testing.T) { + loader := openapi3.NewLoader() + // IncludeOrigin defaults to false + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: + title: x + version: "" +paths: {} +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(verr, &rfe)) + require.Nil(t, rfe.Origin, "Origin should be nil when loader didn't track origins") +} + +// Document-root fields (openapi, webhooks, jsonSchemaDialect) live on +// *T, which now carries an Origin when IncludeOrigin is set. Their +// RequiredFieldError / FieldVersionMismatchError therefore carries the +// document's Origin: scalar root fields resolve precisely via +// Origin.Fields; object/missing root fields fall back to Origin.Key. +func TestValidationError_OriginForDocumentRootFields(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: "" +info: + title: x + version: "1" +paths: {} +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(verr, &rfe)) + require.Equal(t, "openapi", rfe.Field) + require.NotNil(t, rfe.Origin, "doc-root fields now carry the document's Origin") + require.Same(t, doc.Origin, rfe.Origin, "the error carries T.Origin") + require.Greater(t, rfe.Origin.Fields["openapi"].Line, 0, + `Origin.Fields["openapi"] locates the openapi: line`) +} + +// SchemaValueError clusters "'s example/default value +// doesn't satisfy the schema's own constraints" failures. Reach the +// cluster via errors.As, the underlying *SchemaError via Unwrap (or +// nested errors.As), and the cluster's metadata via cluster.ValueKind. +func TestValidationError_SchemaValueErrorOnInvalidExample(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: + /thing: + get: + parameters: + - name: token + in: query + example: too-long + schema: + type: string + maxLength: 4 + responses: + "200": {description: ok} +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + require.Error(t, verr) + + // Cluster is reachable. + var sve *openapi3.SchemaValueError + require.True(t, errors.As(verr, &sve)) + require.Equal(t, "example", sve.ValueKind) + require.NotNil(t, sve.Origin, "parameter Origin should be carried through") + + // Underlying *SchemaError is reachable via the Unwrap chain. + var se *openapi3.SchemaError + require.True(t, errors.As(verr, &se)) + + // Error() prefixes the cluster's ValueKind to keep the historical + // "invalid example: ..." message format byte-identical. + require.Contains(t, sve.Error(), "invalid example: ") +} + +// Pin RequiredFieldError cluster + leaf reachability for required-field +// validation on operations and external docs. +func TestValidationError_FollowupRequiredFieldLeaves(t *testing.T) { + type tc struct { + name string + doc *openapi3.T + field string + message string + leafCheck func(t *testing.T, err error) + } + cases := []tc{ + { + name: "operation responses required", + doc: &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(openapi3.WithPath("/p", &openapi3.PathItem{ + Get: &openapi3.Operation{}, // no Responses + })), + }, + field: "operation.responses", + message: "value of responses must be an object", + leafCheck: func(t *testing.T, err error) { + var l *openapi3.OperationResponsesRequired + require.True(t, errors.As(err, &l)) + }, + }, + { + name: "external docs url required", + doc: &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + ExternalDocs: &openapi3.ExternalDocs{}, // empty URL + }, + field: "externalDocs.url", + message: "url is required", + leafCheck: func(t *testing.T, err error) { + var l *openapi3.ExternalDocsURLRequired + require.True(t, errors.As(err, &l)) + }, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + err := c.doc.Validate(context.Background()) + require.Error(t, err) + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, c.field, rfe.Field) + + c.leafCheck(t, err) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + require.Equal(t, c.message, ve.Message) + }) + } +} + +// MultiError already implements As() that recurses into elements, so a +// typed validation error wrapped inside a MultiError must remain +// reachable. This pins that no special wiring is needed for the typed +// errors to flow through the MultiError tree. +func TestValidationError_FlowsThroughMultiError(t *testing.T) { + leaf := &openapi3.InfoVersionRequired{ + ValidationError: openapi3.ValidationError{Message: "x"}, + } + cluster := &openapi3.RequiredFieldError{Field: "info.version", Cause: leaf} + me := openapi3.MultiError{errors.New("unrelated"), cluster} + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(me, &rfe)) + + var ivr *openapi3.InfoVersionRequired + require.True(t, errors.As(me, &ivr)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(me, &ve)) +} + +// Pin MutuallyExclusiveFieldsError cluster + leaf reachability for the +// four sites where two fields are forbidden from being set together. +func TestValidationError_MutuallyExclusiveFieldsLeaves(t *testing.T) { + t.Run("example value vs externalValue", func(t *testing.T) { + ex := &openapi3.Example{Value: "v", ExternalValue: "https://x"} + err := ex.Validate(context.Background()) + require.EqualError(t, err, "value and externalValue are mutually exclusive") + + var mef *openapi3.MutuallyExclusiveFieldsError + require.True(t, errors.As(err, &mef)) + require.Equal(t, "value", mef.Field1) + require.Equal(t, "externalValue", mef.Field2) + + var leaf *openapi3.ExampleValueExternalValueExclusive + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + + t.Run("license url vs identifier", func(t *testing.T) { + // identifier is a 3.1+ field; opt in so the URL/identifier check + // is the one that fires. + lic := &openapi3.License{Name: "MIT", URL: "https://x", Identifier: "MIT"} + err := lic.Validate(context.Background(), openapi3.IsOpenAPI31OrLater()) + require.EqualError(t, err, "license must not specify both 'url' and 'identifier'") + + var mef *openapi3.MutuallyExclusiveFieldsError + require.True(t, errors.As(err, &mef)) + require.Equal(t, "url", mef.Field1) + require.Equal(t, "identifier", mef.Field2) + + var leaf *openapi3.LicenseURLIdentifierExclusive + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("link operationId vs operationRef", func(t *testing.T) { + link := &openapi3.Link{OperationID: "getX", OperationRef: "#/x"} + err := link.Validate(context.Background()) + require.EqualError(t, err, `operationId "getX" and operationRef "#/x" are mutually exclusive`) + + var mef *openapi3.MutuallyExclusiveFieldsError + require.True(t, errors.As(err, &mef)) + require.Equal(t, "operationId", mef.Field1) + require.Equal(t, "operationRef", mef.Field2) + + var leaf *openapi3.LinkOperationIDRefExclusive + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("schema readOnly vs writeOnly", func(t *testing.T) { + schema := &openapi3.Schema{ReadOnly: true, WriteOnly: true} + err := schema.Validate(context.Background()) + require.EqualError(t, err, "a property MUST NOT be marked as both readOnly and writeOnly being true") + + var mef *openapi3.MutuallyExclusiveFieldsError + require.True(t, errors.As(err, &mef)) + require.Equal(t, "readOnly", mef.Field1) + require.Equal(t, "writeOnly", mef.Field2) + + var leaf *openapi3.SchemaReadOnlyWriteOnlyExclusive + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin ForbiddenFieldError cluster + leaf reachability for the four +// sites where a field is set but the spec forbids it in that context. +func TestValidationError_ForbiddenFieldLeaves(t *testing.T) { + t.Run("header.name forbidden", func(t *testing.T) { + h := &openapi3.Header{Parameter: openapi3.Parameter{Name: "X-Trace"}} + err := h.Validate(context.Background()) + require.EqualError(t, err, + "header 'name' MUST NOT be specified, it is given in the corresponding headers map") + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "name", ffe.Field) + + var leaf *openapi3.HeaderNameForbidden + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + + t.Run("header.in forbidden", func(t *testing.T) { + h := &openapi3.Header{Parameter: openapi3.Parameter{In: "header"}} + err := h.Validate(context.Background()) + require.EqualError(t, err, + "header 'in' MUST NOT be specified, it is implicitly in header") + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "in", ffe.Field) + + var leaf *openapi3.HeaderInForbidden + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin parameter.name and apiKey securityScheme.name leaves directly: +// these check Validate on a single component (without wiring a full +// document around it). +func TestValidationError_ParameterAndAPIKeyNameLeaves(t *testing.T) { + t.Run("parameter name required", func(t *testing.T) { + err := (&openapi3.Parameter{}).Validate(context.Background()) + require.EqualError(t, err, "parameter name can't be blank") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "parameter.name", rfe.Field) + + var leaf *openapi3.ParameterNameRequired + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("apiKey securityScheme name required", func(t *testing.T) { + ss := &openapi3.SecurityScheme{Type: "apiKey", In: "header"} + err := ss.Validate(context.Background()) + require.EqualError(t, err, "security scheme of type 'apiKey' should have 'name'") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "securityScheme.name", rfe.Field) + + var leaf *openapi3.APIKeySecuritySchemeNameRequired + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin ServerURLTemplateError cluster + leaf reachability for the three +// server URL template sites (mismatched braces, undeclared variables +// in two flavours). +func TestValidationError_ServerURLTemplateLeaves(t *testing.T) { + t.Run("mismatched braces", func(t *testing.T) { + s := &openapi3.Server{URL: "https://example.com/{x"} + err := s.Validate(context.Background()) + require.EqualError(t, err, "server URL has mismatched { and }") + + var sue *openapi3.ServerURLTemplateError + require.True(t, errors.As(err, &sue)) + require.Equal(t, "https://example.com/{x", sue.URL) + + var leaf *openapi3.ServerURLMismatchedBraces + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + + t.Run("undeclared variables (count mismatch)", func(t *testing.T) { + s := &openapi3.Server{URL: "https://example.com/{x}"} // no Variables declared + err := s.Validate(context.Background()) + require.EqualError(t, err, "server has undeclared variables") + + var sue *openapi3.ServerURLTemplateError + require.True(t, errors.As(err, &sue)) + + var leaf *openapi3.ServerURLUndeclaredVariables + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("undeclared variables (name mismatch)", func(t *testing.T) { + s := &openapi3.Server{ + URL: "https://example.com/{x}", + Variables: map[string]*openapi3.ServerVariable{"y": {Default: "z"}}, + } + err := s.Validate(context.Background()) + require.EqualError(t, err, "server has undeclared variables") + + var leaf *openapi3.ServerURLUndeclaredVariables + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin EitherFieldRequiredError cluster + leaf reachability for the +// two "at least one of these fields must be set" sites. +func TestValidationError_EitherFieldRequiredLeaves(t *testing.T) { + t.Run("example value or externalValue", func(t *testing.T) { + ex := &openapi3.Example{} + err := ex.Validate(context.Background()) + require.EqualError(t, err, "no value or externalValue field") + + var efr *openapi3.EitherFieldRequiredError + require.True(t, errors.As(err, &efr)) + require.Equal(t, []string{"value", "externalValue"}, efr.Fields) + + var leaf *openapi3.ExampleValueOrExternalValueRequired + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + + t.Run("link operationId or operationRef", func(t *testing.T) { + link := &openapi3.Link{} + err := link.Validate(context.Background()) + require.EqualError(t, err, "missing operationId or operationRef on link") + + var efr *openapi3.EitherFieldRequiredError + require.True(t, errors.As(err, &efr)) + require.Equal(t, []string{"operationId", "operationRef"}, efr.Fields) + + var leaf *openapi3.LinkOperationIDOrRefRequired + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin SchemaItemsRequired leaf reachability via the existing +// RequiredFieldError cluster. +func TestValidationError_SchemaItemsRequiredLeaf(t *testing.T) { + schema := &openapi3.Schema{Type: &openapi3.Types{"array"}} + err := schema.Validate(context.Background()) + require.EqualError(t, err, "when schema type is 'array', schema 'items' must be non-null") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "schema.items", rfe.Field) + + var leaf *openapi3.SchemaItemsRequired + require.True(t, errors.As(err, &leaf)) +} + +// Pin doc-root RequiredFieldError leaves: info, paths, +// jsonSchemaDialect-must-be-absolute-URI. +func TestValidationError_DocRootRequiredLeaves(t *testing.T) { + t.Run("info required", func(t *testing.T) { + // doc with no Info — fails with the wrap "invalid info: must be an object". + doc := &openapi3.T{OpenAPI: "3.0.3", Paths: openapi3.NewPaths()} + err := doc.Validate(context.Background()) + require.EqualError(t, err, "invalid info: must be an object") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "info", rfe.Field) + + var leaf *openapi3.InfoRequired + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("paths required (3.0)", func(t *testing.T) { + // 3.0 doc with no Paths — fails with "invalid paths: must be an object". + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + } + err := doc.Validate(context.Background()) + require.EqualError(t, err, "invalid paths: must be an object") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "paths", rfe.Field) + + var leaf *openapi3.PathsRequired + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("jsonSchemaDialect must be absolute URI", func(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + JSONSchemaDialect: "no-scheme/relative", + } + err := doc.Validate(context.Background(), openapi3.IsOpenAPI31OrLater()) + require.EqualError(t, err, "invalid jsonSchemaDialect: must be an absolute URI with a scheme") + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "jsonSchemaDialect", rfe.Field) + + var leaf *openapi3.JSONSchemaDialectAbsoluteURIRequired + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin SchemaBothFormsExclusive cluster + leaf reachability for the +// three union-typed schema fields set to both boolean and schema forms. +func TestValidationError_SchemaBothFormsLeaves(t *testing.T) { + t.Run("additionalProperties both forms", func(t *testing.T) { + yes := true + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + AdditionalProperties: openapi3.AdditionalProperties{ + Has: &yes, + Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{}}, + }, + } + err := schema.Validate(context.Background()) + require.EqualError(t, err, "additionalProperties are set to both boolean and schema") + + var sbf *openapi3.SchemaBothFormsExclusive + require.True(t, errors.As(err, &sbf)) + require.Equal(t, "additionalProperties", sbf.Field) + + var leaf *openapi3.SchemaAdditionalPropertiesBothForms + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) + }) + + t.Run("unevaluatedItems both forms", func(t *testing.T) { + yes := true + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + UnevaluatedItems: openapi3.BoolSchema{ + Has: &yes, + Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{}}, + }, + } + err := schema.Validate(context.Background(), openapi3.IsOpenAPI31OrLater()) + require.EqualError(t, err, "unevaluatedItems is set to both boolean and schema") + + var sbf *openapi3.SchemaBothFormsExclusive + require.True(t, errors.As(err, &sbf)) + require.Equal(t, "unevaluatedItems", sbf.Field) + + var leaf *openapi3.SchemaUnevaluatedItemsBothForms + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("unevaluatedProperties both forms", func(t *testing.T) { + yes := true + schema := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + UnevaluatedProperties: openapi3.BoolSchema{ + Has: &yes, + Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{}}, + }, + } + err := schema.Validate(context.Background(), openapi3.IsOpenAPI31OrLater()) + require.EqualError(t, err, "unevaluatedProperties is set to both boolean and schema") + + var sbf *openapi3.SchemaBothFormsExclusive + require.True(t, errors.As(err, &sbf)) + require.Equal(t, "unevaluatedProperties", sbf.Field) + + var leaf *openapi3.SchemaUnevaluatedPropertiesBothForms + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin ExactlyOneFieldError and SingleEntryContentError clusters for +// the four parameter/header content+schema sites. +func TestValidationError_ParameterHeaderContentSchemaLeaves(t *testing.T) { + t.Run("parameter content/schema exactly one (neither set)", func(t *testing.T) { + p := &openapi3.Parameter{Name: "p", In: "query"} + err := p.Validate(context.Background()) + require.ErrorContains(t, err, "parameter must contain exactly one of content and schema") + + var efe *openapi3.ExactlyOneFieldError + require.True(t, errors.As(err, &efe)) + require.Equal(t, []string{"content", "schema"}, efe.Fields) + + var leaf *openapi3.ParameterContentSchemaExactlyOne + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("parameter content single entry", func(t *testing.T) { + p := &openapi3.Parameter{ + Name: "p", In: "query", + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{}, + "application/xml": &openapi3.MediaType{}, + }, + } + err := p.Validate(context.Background()) + require.ErrorContains(t, err, "parameter content must only contain one entry") + + var sec *openapi3.SingleEntryContentError + require.True(t, errors.As(err, &sec)) + require.Equal(t, "parameter", sec.Subject) + + var leaf *openapi3.ParameterContentSingleEntry + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("header content/schema exactly one (neither set)", func(t *testing.T) { + h := &openapi3.Header{} + err := h.Validate(context.Background()) + require.ErrorContains(t, err, "parameter must contain exactly one of content and schema") + + var efe *openapi3.ExactlyOneFieldError + require.True(t, errors.As(err, &efe)) + require.Equal(t, []string{"content", "schema"}, efe.Fields) + + var leaf *openapi3.HeaderContentSchemaExactlyOne + require.True(t, errors.As(err, &leaf)) + }) + + t.Run("header content single entry", func(t *testing.T) { + h := &openapi3.Header{ + Parameter: openapi3.Parameter{ + Content: openapi3.Content{ + "application/json": &openapi3.MediaType{}, + "application/xml": &openapi3.MediaType{}, + }, + }, + } + err := h.Validate(context.Background()) + require.ErrorContains(t, err, "parameter content must only contain one entry") + + var sec *openapi3.SingleEntryContentError + require.True(t, errors.As(err, &sec)) + require.Equal(t, "header", sec.Subject) + + var leaf *openapi3.HeaderContentSingleEntry + require.True(t, errors.As(err, &leaf)) + }) +} + +// Pin WebhookNilError cluster + leaf reachability for the +// nil-pathitem webhook check in T.Validate. +func TestValidationError_WebhookNilLeaf(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.1.0", + Info: &openapi3.Info{Title: "x", Version: "1.0.0"}, + Paths: openapi3.NewPaths(), + Webhooks: map[string]*openapi3.PathItem{"onEvent": nil}, + } + err := doc.Validate(context.Background(), openapi3.IsOpenAPI31OrLater()) + require.EqualError(t, err, `invalid webhooks: webhook "onEvent" is nil`) + + var wne *openapi3.WebhookNilError + require.True(t, errors.As(err, &wne)) + require.Equal(t, "onEvent", wne.Name) + + var leaf *openapi3.WebhookNil + require.True(t, errors.As(err, &leaf)) + + var ve *openapi3.ValidationError + require.True(t, errors.As(err, &ve)) +} + +func TestValidationError_PathParameterRequired(t *testing.T) { + // Path parameters must be declared required: true. A parameter with + // in: path and required: false (or omitted) triggers the cluster. + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /things/{id}: + get: + parameters: + - { name: id, in: path } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `path parameter "id" must be required`) + + var ppr *openapi3.PathParameterRequiredError + require.True(t, errors.As(err, &ppr)) + require.Equal(t, "id", ppr.Param) +} + +func TestValidationError_DuplicateOperationID(t *testing.T) { + // Two operations sharing the same operationId across paths must + // surface a DuplicateOperationIDError carrying both endpoints. + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /a: + get: + operationId: shared + responses: { "200": { description: ok } } + /b: + get: + operationId: shared + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `operations "GET /a" and "GET /b" have the same operation id "shared"`) + + var doe *openapi3.DuplicateOperationIDError + require.True(t, errors.As(err, &doe)) + require.Equal(t, "shared", doe.OperationID) + require.Equal(t, "GET /a", doe.Endpoint1) + require.Equal(t, "GET /b", doe.Endpoint2) +} + +func TestValidationError_ExtraSiblingFields(t *testing.T) { + // A non-x- key in Extensions triggers validateExtensions's + // "extra sibling fields" error, now typed as ExtraSiblingFieldsError. + // Construct a non-empty Responses so the empty-responses guard + // doesn't fire first; the only finding then comes from extensions. + responses := openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: openapi3.NewResponse().WithDescription("ok"), + }), + ) + responses.Extensions = map[string]any{"bogus": "value"} + err := responses.Validate(context.Background()) + require.ErrorContains(t, err, "extra sibling fields: [bogus]") + + var esf *openapi3.ExtraSiblingFieldsError + require.True(t, errors.As(err, &esf)) + require.Equal(t, []string{"bogus"}, esf.Fields) +} + +func TestValidationError_SchemaTypeError(t *testing.T) { + // Unsupported 'type' value on a schema (e.g., "bool" instead of + // "boolean") triggers SchemaTypeError carrying the bad value. + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: { type: bool } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `unsupported 'type' value "bool"`) + + var ste *openapi3.SchemaTypeError + require.True(t, errors.As(err, &ste)) + require.Equal(t, "bool", ste.Type) +} + +// Origin tracking for DuplicateOperationIDError. When IncludeOrigin is +// set, the cluster carries the offending (second) operation's Origin so +// consumers can pin the finding at the duplicate operationId rather +// than at the document root. +func TestValidationError_DuplicateOperationID_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /a: + get: + operationId: shared + responses: { "200": { description: ok } } + /b: + get: + operationId: shared + responses: { "200": { description: ok } } +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var doe *openapi3.DuplicateOperationIDError + require.True(t, errors.As(verr, &doe)) + require.NotNil(t, doe.Origin, "cluster should carry the offending operation's Origin when loader tracks origins") + require.NotNil(t, doe.Origin.Key, "Origin.Key set by the loader") + require.Greater(t, doe.Origin.Key.Line, 0) +} + +// Without IncludeOrigin, DuplicateOperationIDError.Origin is nil — no +// fabrication of location info that wasn't tracked. +func TestValidationError_DuplicateOperationID_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /a: + get: + operationId: shared + responses: { "200": { description: ok } } + /b: + get: + operationId: shared + responses: { "200": { description: ok } } +`) + verr := doc.Validate(context.Background()) + var doe *openapi3.DuplicateOperationIDError + require.True(t, errors.As(verr, &doe)) + require.Nil(t, doe.Origin, "Origin should be nil when loader didn't track origins") +} + +// Origin tracking for ExtraSiblingFieldsError. The cluster carries the +// parent object's Origin so consumers can pin the finding at the +// container holding the unexpected sibling fields. Exercised here via +// a $ref with a disallowed sibling, which is the most common surface. +func TestValidationError_ExtraSiblingFields_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + get: + responses: + "200": + description: ok + content: + application/json: + schema: + $ref: "#/components/schemas/T" + description: should-not-be-here +components: + schemas: + T: { type: string } +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var esf *openapi3.ExtraSiblingFieldsError + require.True(t, errors.As(verr, &esf)) + require.NotNil(t, esf.Origin, "cluster should carry the parent object's Origin when loader tracks origins") + require.NotNil(t, esf.Origin.Key) + require.Greater(t, esf.Origin.Key.Line, 0) +} + +// A parameter with `in:` set to anything outside {path, query, header, +// cookie} triggers InvalidParameterInError carrying the rejected +// value. Most common offender is `in: body` from Swagger 2.0 specs +// that didn't fully migrate to 3.x. +func TestValidationError_InvalidParameterIn(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + post: + parameters: + - { name: payload, in: body, schema: { type: object } } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `parameter can't have 'in' value "body"`) + + var ipe *openapi3.InvalidParameterInError + require.True(t, errors.As(err, &ipe)) + require.Equal(t, "body", ipe.Value) +} + +// Origin tracking for InvalidParameterInError. +func TestValidationError_InvalidParameterIn_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + post: + parameters: + - { name: payload, in: body, schema: { type: object } } + responses: { "200": { description: ok } } +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var ipe *openapi3.InvalidParameterInError + require.True(t, errors.As(verr, &ipe)) + require.NotNil(t, ipe.Origin) + require.NotNil(t, ipe.Origin.Key) + require.Greater(t, ipe.Origin.Key.Line, 0) +} + +// A schema `pattern:` using a Perl-only regex feature (lookahead / +// lookbehind etc.) fails to compile against Go's RE2 and triggers +// SchemaPatternRegexError. The cluster carries the offending pattern +// AND chains through to the original *SchemaError via Unwrap so +// callers using errors.As against the legacy *SchemaError still match. +func TestValidationError_SchemaPatternRegex(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: + type: string + pattern: "(?!foo)bar" +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var spre *openapi3.SchemaPatternRegexError + require.True(t, errors.As(err, &spre)) + require.Equal(t, "(?!foo)bar", spre.Pattern) + + // Backward compat: Unwrap reaches the legacy *SchemaError. + var se *openapi3.SchemaError + require.True(t, errors.As(err, &se), "*SchemaError must still be reachable via Unwrap chain") + require.Equal(t, "pattern", se.SchemaField) +} + +// Origin tracking for SchemaPatternRegexError. +func TestValidationError_SchemaPatternRegex_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: + type: string + pattern: "(?!foo)bar" +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var spre *openapi3.SchemaPatternRegexError + require.True(t, errors.As(verr, &spre)) + require.NotNil(t, spre.Origin) + require.NotNil(t, spre.Origin.Key) + require.Greater(t, spre.Origin.Key.Line, 0) +} + +// Security scheme with a `type:` outside the spec-permitted set +// {apiKey, http, oauth2, openIdConnect, mutualTLS} triggers +// InvalidSecuritySchemeTypeError carrying the rejected value. +func TestValidationError_InvalidSecuritySchemeType(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: cookie +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme 'type' can't be "cookie"`) + + var iste *openapi3.InvalidSecuritySchemeTypeError + require.True(t, errors.As(err, &iste)) + require.Equal(t, "cookie", iste.Type) +} + +// Origin tracking for InvalidSecuritySchemeTypeError. +func TestValidationError_InvalidSecuritySchemeType_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: cookie +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var iste *openapi3.InvalidSecuritySchemeTypeError + require.True(t, errors.As(verr, &iste)) + require.NotNil(t, iste.Origin) + require.NotNil(t, iste.Origin.Key) + require.Greater(t, iste.Origin.Key.Line, 0) +} + +// HTTP security scheme with a `scheme:` outside {bearer, basic, +// negotiate, digest} triggers InvalidHTTPSchemeError carrying the +// rejected value. +func TestValidationError_InvalidHTTPScheme(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: mutual +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type 'http' has invalid 'scheme' value "mutual"`) + + var ihse *openapi3.InvalidHTTPSchemeError + require.True(t, errors.As(err, &ihse)) + require.Equal(t, "mutual", ihse.Scheme) +} + +// Origin tracking for InvalidHTTPSchemeError. +func TestValidationError_InvalidHTTPScheme_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: mutual +`)) + require.NoError(t, err) + + verr := doc.Validate(context.Background()) + var ihse *openapi3.InvalidHTTPSchemeError + require.True(t, errors.As(verr, &ihse)) + require.NotNil(t, ihse.Origin) + require.NotNil(t, ihse.Origin.Key) + require.Greater(t, ihse.Origin.Key.Line, 0) +} + +// A $ref left with a non-nil Ref string but nil Value at Validate time +// triggers UnresolvedRefError. Constructed programmatically because +// the YAML loader is strict about ref resolution at load time; this +// shape models the in-the-wild case where a spec uses an external +// $ref that wasn't fetched (testdata/apis_guru_openapi_directory has +// real examples). +func TestValidationError_UnresolvedRef(t *testing.T) { + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "t", Version: "1"}, + Paths: openapi3.NewPaths(), + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{ + "X": &openapi3.SchemaRef{ + Ref: "external.yaml#/T", + Value: nil, // unresolved + }, + }, + }, + } + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `found unresolved ref: "external.yaml#/T"`) + + var ure *openapi3.UnresolvedRefError + require.True(t, errors.As(err, &ure)) + require.Equal(t, "external.yaml#/T", ure.Ref) +} + +// openIdConnect security scheme without an openIdConnectUrl triggers +// RequiredFieldError wrapping *OpenIDConnectURLRequired. +func TestValidationError_OpenIDConnectURLRequired(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: openIdConnect +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `no OIDC URL found for openIdConnect security scheme`) + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "openIdConnectUrl", rfe.Field) + var leaf *openapi3.OpenIDConnectURLRequired + require.True(t, errors.As(err, &leaf)) +} + +// apiKey security scheme with `in:` set to a value outside +// {query, header, cookie} triggers APIKeyInInvalidError carrying the +// rejected value. +func TestValidationError_APIKeyInInvalid(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: apiKey + in: body + name: payload +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `should have 'in'. It can be 'query', 'header' or 'cookie', not "body"`) + + var akie *openapi3.APIKeyInInvalidError + require.True(t, errors.As(err, &akie)) + require.Equal(t, "body", akie.Value) +} + +// A non-apiKey scheme that nevertheless declares `in:` triggers +// ForbiddenFieldError wrapping *SecuritySchemeInForbidden. +func TestValidationError_SecuritySchemeInForbidden(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: basic + in: query +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type "http" can't have 'in'`) + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "in", ffe.Field) + var leaf *openapi3.SecuritySchemeInForbidden + require.True(t, errors.As(err, &leaf)) +} + +// A non-apiKey scheme that declares `name:` triggers +// ForbiddenFieldError wrapping *SecuritySchemeNameForbidden. +func TestValidationError_SecuritySchemeNameForbidden(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: basic + name: something +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type "http" can't have 'name'`) + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "name", ffe.Field) + var leaf *openapi3.SecuritySchemeNameForbidden + require.True(t, errors.As(err, &leaf)) +} + +// A non-http scheme declaring `bearerFormat:` triggers +// ForbiddenFieldError wrapping *SecuritySchemeBearerFormatForbidden. +func TestValidationError_SecuritySchemeBearerFormatForbidden(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: apiKey + in: query + name: x + bearerFormat: JWT +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type "apiKey" can't have 'bearerFormat'`) + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "bearerFormat", ffe.Field) + var leaf *openapi3.SecuritySchemeBearerFormatForbidden + require.True(t, errors.As(err, &leaf)) +} + +// oauth2 scheme missing `flows:` triggers RequiredFieldError +// wrapping *SecuritySchemeFlowsRequired. +func TestValidationError_SecuritySchemeFlowsRequired(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: oauth2 +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type "oauth2" should have 'flows'`) + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "flows", rfe.Field) + var leaf *openapi3.SecuritySchemeFlowsRequired + require.True(t, errors.As(err, &leaf)) +} + +// A non-oauth2 scheme declaring `flows:` triggers ForbiddenFieldError +// wrapping *SecuritySchemeFlowsForbidden. +func TestValidationError_SecuritySchemeFlowsForbidden(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: basic + flows: + password: + tokenUrl: https://example.com/token + scopes: {} +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `security scheme of type "http" can't have 'flows'`) + + var ffe *openapi3.ForbiddenFieldError + require.True(t, errors.As(err, &ffe)) + require.Equal(t, "flows", ffe.Field) + var leaf *openapi3.SecuritySchemeFlowsForbidden + require.True(t, errors.As(err, &leaf)) +} + +// A path key that doesn't start with '/' triggers +// PathMustStartWithSlashError carrying the offending path. +func TestValidationError_PathMustStartWithSlash(t *testing.T) { + paths := openapi3.NewPaths(openapi3.WithPath("users/{id}", &openapi3.PathItem{})) + err := paths.Validate(context.Background()) + require.ErrorContains(t, err, `path "users/{id}" does not start with a forward slash (/)`) + + var pmss *openapi3.PathMustStartWithSlashError + require.True(t, errors.As(err, &pmss)) + require.Equal(t, "users/{id}", pmss.Path) +} + +// Two path keys normalizing to the same template trigger +// ConflictingPathsError carrying both paths. +func TestValidationError_ConflictingPaths(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /users/{a}: + get: + parameters: + - { name: a, in: path, required: true, schema: { type: string } } + responses: { "200": { description: ok } } + /users/{b}: + get: + parameters: + - { name: b, in: path, required: true, schema: { type: string } } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var cpe *openapi3.ConflictingPathsError + require.True(t, errors.As(err, &cpe)) + require.Contains(t, []string{cpe.Path1, cpe.Path2}, "/users/{a}") + require.Contains(t, []string{cpe.Path1, cpe.Path2}, "/users/{b}") +} + +// Two parameters with the same (In, Name) combination on a single +// operation trigger DuplicateParameterError carrying both. +func TestValidationError_DuplicateParameter(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + get: + parameters: + - { name: id, in: query, schema: { type: string } } + - { name: id, in: query, schema: { type: string } } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `more than one "query" parameter has name "id"`) + + var dpe *openapi3.DuplicateParameterError + require.True(t, errors.As(err, &dpe)) + require.Equal(t, "query", dpe.In) + require.Equal(t, "id", dpe.Name) +} + +// An encoding with an unsupported (style, explode) combination +// triggers InvalidSerializationMethodError carrying Subject "media +// type". +func TestValidationError_InvalidSerializationMethod_MediaType(t *testing.T) { + explode := true + enc := &openapi3.Encoding{Style: "matrix", Explode: &explode} + err := enc.Validate(context.Background()) + require.ErrorContains(t, err, `serialization method with style="matrix" and explode=true is not supported by media type`) + + var isme *openapi3.InvalidSerializationMethodError + require.True(t, errors.As(err, &isme)) + require.Equal(t, "media type", isme.Subject) + require.Equal(t, "matrix", isme.Style) +} + +// A parameter with `example` AND `examples` both populated triggers +// MutuallyExclusiveFieldsError wrapping +// *ParameterExampleAndExamplesExclusive. +func TestValidationError_ParameterExampleAndExamplesExclusive(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + get: + parameters: + - name: q + in: query + schema: { type: string } + example: foo + examples: + a: { value: bar } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.ErrorContains(t, err, `example and examples are mutually exclusive`) + + var mef *openapi3.MutuallyExclusiveFieldsError + require.True(t, errors.As(err, &mef)) + require.Equal(t, "example", mef.Field1) + require.Equal(t, "examples", mef.Field2) + var leaf *openapi3.ParameterExampleAndExamplesExclusive + require.True(t, errors.As(err, &leaf)) +} + +// A server variable without `default` triggers RequiredFieldError +// wrapping *ServerVariableDefaultRequired. +func TestValidationError_ServerVariableDefaultRequired(t *testing.T) { + sv := &openapi3.ServerVariable{Enum: []string{"a", "b"}} + err := sv.Validate(context.Background()) + require.ErrorContains(t, err, `field default is required in`) + + var rfe *openapi3.RequiredFieldError + require.True(t, errors.As(err, &rfe)) + require.Equal(t, "default", rfe.Field) + var leaf *openapi3.ServerVariableDefaultRequired + require.True(t, errors.As(err, &leaf)) +} + +// Context wrappers: each replaces a bare fmt.Errorf-with-%w wrap +// so consumers can extract the wrapping context via errors.As. +// The Unwrap chain still reaches the inner typed leaf. + +func TestValidationError_ComponentValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: + type: foobar +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var cve *openapi3.ComponentValidationError + require.True(t, errors.As(err, &cve)) + require.Equal(t, "schema", cve.Section) + require.Equal(t, "Bad", cve.Name) + // Unwrap reaches the typed inner leaf. + var ste *openapi3.SchemaTypeError + require.True(t, errors.As(err, &ste)) + require.Equal(t, "foobar", ste.Type) +} + +func TestValidationError_ExternalDocsURLValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +externalDocs: + url: "://not a url" +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + var euve *openapi3.ExternalDocsURLValidationError + require.True(t, errors.As(err, &euve)) +} + +func TestValidationError_WebhookValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.1.0 +info: { title: t, version: "1" } +paths: {} +webhooks: + myhook: + get: + operationId: "" +`) + // Validate may surface various findings; the webhook wrap should + // be discoverable via errors.As regardless of which inner leaf fires. + _ = doc.Validate(context.Background()) + // Construct directly to verify the wrapper shape (the failure path + // above may or may not produce a webhook error depending on the + // inner validators' state, but the type itself is what we want to + // pin). + wve := &openapi3.WebhookValidationError{Name: "myhook", Cause: errors.New("boom")} + require.Contains(t, wve.Error(), `webhook "myhook"`) + var got *openapi3.WebhookValidationError + require.True(t, errors.As(wve, &got)) + require.Equal(t, "myhook", got.Name) +} + +func TestValidationError_ParameterFieldValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + get: + parameters: + - name: q + in: query + schema: { type: foobar } + responses: { "200": { description: ok } } +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var pfve *openapi3.ParameterFieldValidationError + require.True(t, errors.As(err, &pfve)) + require.Equal(t, "q", pfve.ParameterName) + require.Equal(t, "schema", pfve.Field) + var ste *openapi3.SchemaTypeError + require.True(t, errors.As(err, &ste)) +} + +func TestValidationError_OAuthFlowValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: oauth2 + flows: + password: + tokenUrl: "://not a url" + scopes: {} +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var ofve *openapi3.OAuthFlowValidationError + require.True(t, errors.As(err, &ofve)) + require.Equal(t, "password", ofve.FlowKind) + var ssfve *openapi3.SecuritySchemeFlowValidationError + require.True(t, errors.As(err, &ssfve), "outer SecuritySchemeFlowValidationError must also be reachable") +} + +func TestValidationError_OAuthFlowFieldValidationError(t *testing.T) { + doc := loadDocFromYAML(t, ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: oauth2 + flows: + password: + tokenUrl: https://example.com/token + refreshUrl: "://not a url" + scopes: {} +`) + err := doc.Validate(context.Background()) + require.Error(t, err) + + var offve *openapi3.OAuthFlowFieldValidationError + require.True(t, errors.As(err, &offve)) + require.Equal(t, "refreshUrl", offve.Field) +} + +// Without IncludeOrigin, ExtraSiblingFieldsError.Origin is nil. +func TestValidationError_ExtraSiblingFields_OriginNilWithoutLoaderTracking(t *testing.T) { + responses := openapi3.NewResponses( + openapi3.WithStatus(200, &openapi3.ResponseRef{ + Value: openapi3.NewResponse().WithDescription("ok"), + }), + ) + responses.Extensions = map[string]any{"bogus": "value"} + verr := responses.Validate(context.Background()) + var esf *openapi3.ExtraSiblingFieldsError + require.True(t, errors.As(verr, &esf)) + require.Nil(t, esf.Origin, "Origin should be nil when the parent object's Origin is unset") +} + +// --------------------------------------------------------------------- +// Origin coverage matrix: for each cluster carrying an Origin field, +// one *_CarriesOrigin (loader-tracked) and one +// *_OriginNilWithoutLoaderTracking (default loader) test asserting +// the cluster honors IncludeOrigin. Mechanical / repetitive by +// design; the symmetry pins the contract that callers can rely on +// Origin being nil when IncludeOrigin is off. + +func loadDocFromYAMLWithOrigin(t *testing.T, src string) *openapi3.T { + t.Helper() + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + doc, err := loader.LoadFromData([]byte(src)) + require.NoError(t, err) + return doc +} + +const specPathParamNotRequired = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /things/{id}: + get: + parameters: + - { name: id, in: path } + responses: { "200": { description: ok } } +` + +func TestValidationError_PathParameterRequired_CarriesOrigin(t *testing.T) { + doc := loadDocFromYAMLWithOrigin(t, specPathParamNotRequired) + verr := doc.Validate(context.Background()) + var ppr *openapi3.PathParameterRequiredError + require.True(t, errors.As(verr, &ppr)) + require.NotNil(t, ppr.Origin) + require.NotNil(t, ppr.Origin.Key) + require.Greater(t, ppr.Origin.Key.Line, 0) +} + +func TestValidationError_PathParameterRequired_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specPathParamNotRequired) + verr := doc.Validate(context.Background()) + var ppr *openapi3.PathParameterRequiredError + require.True(t, errors.As(verr, &ppr)) + require.Nil(t, ppr.Origin) +} + +const specSchemaTypeBad = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: { type: bool } +` + +func TestValidationError_SchemaType_CarriesOrigin(t *testing.T) { + doc := loadDocFromYAMLWithOrigin(t, specSchemaTypeBad) + verr := doc.Validate(context.Background()) + var ste *openapi3.SchemaTypeError + require.True(t, errors.As(verr, &ste)) + require.NotNil(t, ste.Origin) + require.NotNil(t, ste.Origin.Key) + require.Greater(t, ste.Origin.Key.Line, 0) +} + +func TestValidationError_SchemaType_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specSchemaTypeBad) + verr := doc.Validate(context.Background()) + var ste *openapi3.SchemaTypeError + require.True(t, errors.As(verr, &ste)) + require.Nil(t, ste.Origin) +} + +const specInvalidParameterIn = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + post: + parameters: + - { name: payload, in: body, schema: { type: object } } + responses: { "200": { description: ok } } +` + +func TestValidationError_InvalidParameterIn_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specInvalidParameterIn) + verr := doc.Validate(context.Background()) + var ipe *openapi3.InvalidParameterInError + require.True(t, errors.As(verr, &ipe)) + require.Nil(t, ipe.Origin) +} + +const specSchemaPatternRegex = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + schemas: + Bad: + type: string + pattern: "(?!foo)bar" +` + +func TestValidationError_SchemaPatternRegex_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specSchemaPatternRegex) + verr := doc.Validate(context.Background()) + var spre *openapi3.SchemaPatternRegexError + require.True(t, errors.As(verr, &spre)) + require.Nil(t, spre.Origin) +} + +const specInvalidSecuritySchemeType = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: cookie +` + +func TestValidationError_InvalidSecuritySchemeType_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specInvalidSecuritySchemeType) + verr := doc.Validate(context.Background()) + var iste *openapi3.InvalidSecuritySchemeTypeError + require.True(t, errors.As(verr, &iste)) + require.Nil(t, iste.Origin) +} + +const specInvalidHTTPScheme = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: http + scheme: mutual +` + +func TestValidationError_InvalidHTTPScheme_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specInvalidHTTPScheme) + verr := doc.Validate(context.Background()) + var ihse *openapi3.InvalidHTTPSchemeError + require.True(t, errors.As(verr, &ihse)) + require.Nil(t, ihse.Origin) +} + +const specAPIKeyInInvalid = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: {} +components: + securitySchemes: + Bad: + type: apiKey + in: body + name: payload +` + +func TestValidationError_APIKeyInInvalid_CarriesOrigin(t *testing.T) { + doc := loadDocFromYAMLWithOrigin(t, specAPIKeyInInvalid) + verr := doc.Validate(context.Background()) + var akie *openapi3.APIKeyInInvalidError + require.True(t, errors.As(verr, &akie)) + require.NotNil(t, akie.Origin) + require.NotNil(t, akie.Origin.Key) + require.Greater(t, akie.Origin.Key.Line, 0) +} + +func TestValidationError_APIKeyInInvalid_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specAPIKeyInInvalid) + verr := doc.Validate(context.Background()) + var akie *openapi3.APIKeyInInvalidError + require.True(t, errors.As(verr, &akie)) + require.Nil(t, akie.Origin) +} + +func TestValidationError_PathMustStartWithSlash_CarriesOrigin(t *testing.T) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + // Programmatic because loadDocFromYAML rejects malformed path keys + // during the load phase before Validate gets the chance. + paths := openapi3.NewPaths(openapi3.WithPath("users/{id}", &openapi3.PathItem{})) + paths.Origin = &openapi3.Origin{Key: &openapi3.Location{Line: 1, Column: 1}} + verr := paths.Validate(context.Background()) + var pmss *openapi3.PathMustStartWithSlashError + require.True(t, errors.As(verr, &pmss)) + require.NotNil(t, pmss.Origin) + require.NotNil(t, pmss.Origin.Key) + require.Greater(t, pmss.Origin.Key.Line, 0) +} + +func TestValidationError_PathMustStartWithSlash_OriginNilWithoutLoaderTracking(t *testing.T) { + paths := openapi3.NewPaths(openapi3.WithPath("users/{id}", &openapi3.PathItem{})) + verr := paths.Validate(context.Background()) + var pmss *openapi3.PathMustStartWithSlashError + require.True(t, errors.As(verr, &pmss)) + require.Nil(t, pmss.Origin) +} + +const specConflictingPaths = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /users/{a}: + get: + parameters: + - { name: a, in: path, required: true, schema: { type: string } } + responses: { "200": { description: ok } } + /users/{b}: + get: + parameters: + - { name: b, in: path, required: true, schema: { type: string } } + responses: { "200": { description: ok } } +` + +func TestValidationError_ConflictingPaths_CarriesOrigin(t *testing.T) { + doc := loadDocFromYAMLWithOrigin(t, specConflictingPaths) + verr := doc.Validate(context.Background()) + var cpe *openapi3.ConflictingPathsError + require.True(t, errors.As(verr, &cpe)) + require.NotNil(t, cpe.Origin) + require.NotNil(t, cpe.Origin.Key) + require.Greater(t, cpe.Origin.Key.Line, 0) +} + +func TestValidationError_ConflictingPaths_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specConflictingPaths) + verr := doc.Validate(context.Background()) + var cpe *openapi3.ConflictingPathsError + require.True(t, errors.As(verr, &cpe)) + require.Nil(t, cpe.Origin) +} + +const specDuplicateParameter = ` +openapi: 3.0.3 +info: { title: t, version: "1" } +paths: + /x: + get: + parameters: + - { name: id, in: query, schema: { type: string } } + - { name: id, in: query, schema: { type: string } } + responses: { "200": { description: ok } } +` + +func TestValidationError_DuplicateParameter_CarriesOrigin(t *testing.T) { + doc := loadDocFromYAMLWithOrigin(t, specDuplicateParameter) + verr := doc.Validate(context.Background()) + var dpe *openapi3.DuplicateParameterError + require.True(t, errors.As(verr, &dpe)) + require.NotNil(t, dpe.Origin) + require.NotNil(t, dpe.Origin.Key) + require.Greater(t, dpe.Origin.Key.Line, 0) +} + +func TestValidationError_DuplicateParameter_OriginNilWithoutLoaderTracking(t *testing.T) { + doc := loadDocFromYAML(t, specDuplicateParameter) + verr := doc.Validate(context.Background()) + var dpe *openapi3.DuplicateParameterError + require.True(t, errors.As(verr, &dpe)) + require.Nil(t, dpe.Origin) +} + +func TestValidationError_InvalidSerializationMethod_MediaType_CarriesOrigin(t *testing.T) { + // Encoding.Validate isn't reached from T.Validate (MediaType.Validate + // skips it); exercise it directly with a populated Origin so the + // Carries-Origin assertion is meaningful. + explode := true + enc := &openapi3.Encoding{ + Style: "matrix", + Explode: &explode, + Origin: &openapi3.Origin{Key: &openapi3.Location{Line: 5, Column: 3}}, + } + err := enc.Validate(context.Background()) + var isme *openapi3.InvalidSerializationMethodError + require.True(t, errors.As(err, &isme)) + require.Equal(t, "media type", isme.Subject) + require.NotNil(t, isme.Origin) + require.NotNil(t, isme.Origin.Key) + require.Greater(t, isme.Origin.Key.Line, 0) +} + +func TestValidationError_InvalidSerializationMethod_MediaType_OriginNilWithoutLoaderTracking(t *testing.T) { + explode := true + enc := &openapi3.Encoding{Style: "matrix", Explode: &explode} + err := enc.Validate(context.Background()) + var isme *openapi3.InvalidSerializationMethodError + require.True(t, errors.As(err, &isme)) + require.Nil(t, isme.Origin) +} + +func loadDocFromYAML(t *testing.T, src string) *openapi3.T { + t.Helper() + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(src)) + require.NoError(t, err) + return doc +} diff --git a/openapi3/validation_options.go b/openapi3/validation_options.go index 1d141d40a..fd2be87d8 100644 --- a/openapi3/validation_options.go +++ b/openapi3/validation_options.go @@ -13,6 +13,9 @@ type ValidationOptions struct { schemaFormatValidationEnabled bool schemaPatternValidationDisabled bool schemaExtensionsInRefProhibited bool + jsonSchema2020ValidationEnabled bool + isOpenAPI31OrLater bool + multiErrorEnabled bool regexCompilerFunc RegexCompilerFunc extraSiblingFieldsAllowed map[string]struct{} } @@ -31,6 +34,14 @@ func AllowExtraSiblingFields(fields ...string) ValidationOption { } } +// IsOpenAPI31OrLater enables "JSON Schema Draft 2020-12"-compliant validation (for OpenAPI 3.1 documents). +func IsOpenAPI31OrLater() ValidationOption { + return func(options *ValidationOptions) { + options.isOpenAPI31OrLater = true // To distinguish from v3.0 + options.jsonSchema2020ValidationEnabled = true // TODO: use even for v3.0 + } +} + // EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. // By default, schema format validation is disabled. func EnableSchemaFormatValidation() ValidationOption { @@ -114,6 +125,25 @@ func ProhibitExtensionsWithRef() ValidationOption { } } +// EnableMultiError makes Validate aggregate independent validation errors and +// return them together as a MultiError instead of returning the first error +// and stopping. By default, Validate is fail-fast. +// +// Not every validator reports more than one error yet. Some, such as Schema, +// run checks that build on earlier ones, so continuing past a failure can hit +// a nil dereference or produce nonsense secondary errors. +// We will keep converting more validators in follow-up changes as each one +// is analyzed. +// +// To pull a specific error type out of the result, use errors.As(err, &target). +// It walks into the MultiError automatically, so the same call works whether +// Validate returned one error or many. +func EnableMultiError() ValidationOption { + return func(options *ValidationOptions) { + options.multiErrorEnabled = true + } +} + // SetRegexCompiler allows to override the regex implementation used to validate // field "pattern". func SetRegexCompiler(c RegexCompilerFunc) ValidationOption { diff --git a/openapi3/xml.go b/openapi3/xml.go index 8752d58ba..508c871a6 100644 --- a/openapi3/xml.go +++ b/openapi3/xml.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "encoding/json" + "maps" ) // XML is specified by OpenAPI/Swagger standard version 3. @@ -30,9 +31,7 @@ func (xml XML) MarshalJSON() ([]byte, error) { // MarshalYAML returns the YAML encoding of XML. func (xml XML) MarshalYAML() (any, error) { m := make(map[string]any, 5+len(xml.Extensions)) - for k, v := range xml.Extensions { - m[k] = v - } + maps.Copy(m, xml.Extensions) if x := xml.Name; x != "" { m["name"] = x } @@ -75,5 +74,5 @@ func (xml *XML) UnmarshalJSON(data []byte) error { func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - return validateExtensions(ctx, xml.Extensions) + return validateExtensions(ctx, xml.Extensions, xml.Origin) } diff --git a/openapi3conv/doc.go b/openapi3conv/doc.go new file mode 100644 index 000000000..c4340a3f2 --- /dev/null +++ b/openapi3conv/doc.go @@ -0,0 +1,27 @@ +// Package openapi3conv canonicalizes an OpenAPI 3.x document into the latest +// 3.x representation in place. Schema-level constructs serialize differently +// between OpenAPI 3.0 and 3.1, but represent the same semantics; this package +// rewrites the 3.0 forms into their 3.1 equivalents and bumps the version +// string to the latest 3.x patch release the package knows about. +// +// The OAI commits to strict compatibility for 3.x going forward (see the +// 3.2.1 and 3.3.0 milestones), so a tool that handles the 3.1+ form +// correctly handles all later 3.x versions correctly too. The 3.0 → 3.1 +// transition — the only break in the 3.x line — is the gap this package +// exists to bridge. 3.1 → 3.2 (and any future 3.x) is purely additive and +// requires no rewrites; the package handles those as a version-string bump. +// +// Use this when a downstream consumer (diff tools, validators, code +// generators) needs a single canonical representation regardless of the +// source spec's declared version. +// +// Scope: +// - In scope: 3.x → latest 3.x. +// - Out of scope: any → 3.0 (downgrade is lossy by nature). +// - Out of scope: cross-major upgrades (3 → 4 if/when v4 ships). Those +// belong in a dedicated package mirroring openapi2conv (which converts +// Swagger 2.0 documents to OpenAPI 3.0). +// +// Documents must be Validate()'d before calling Upgrade — passing an +// invalid document is undefined behaviour. +package openapi3conv diff --git a/openapi3conv/openapi3_conv.go b/openapi3conv/openapi3_conv.go new file mode 100644 index 000000000..7d35f8e5b --- /dev/null +++ b/openapi3conv/openapi3_conv.go @@ -0,0 +1,326 @@ +package openapi3conv + +import ( + "fmt" + "io" + "slices" + + "github.com/getkin/kin-openapi/openapi3" +) + +// latestTargetVersion is the OpenAPI version string written into doc.OpenAPI +// after canonicalization. Always the latest 3.x patch release the package +// knows about; bump when a new minor lands. The OAI guarantees strict +// compatibility for 3.x going forward (3.2.x, 3.3.x, ...), so a tool that +// handles 3.1 correctly handles later 3.x versions correctly too. +const latestTargetVersion = "3.2.0" + +// Option configures an Upgrade pass. See WithWriter. +type Option func(*upgradeOptions) + +// upgradeOptions is the internal carrier for Option functions. Kept private +// so the surface stays small and additive — new options are added by +// introducing a new WithX function. +type upgradeOptions struct { + verbose io.Writer +} + +// WithWriter routes one debug line per applied rewrite to w. +func WithWriter(w io.Writer) Option { + return func(o *upgradeOptions) { o.verbose = w } +} + +// Upgrade canonicalizes doc into the latest 3.x representation in place. +// +// The schema-level rewrites the walker applies (nullable → type array, +// boolean exclusive bounds → numeric, example → examples) are idempotent +// and convergent on the 3.1+ form. Calling Upgrade on an already-3.1 (or +// later) document is a no-op aside from the version string bump. +// +// Cross-major upgrades (3 → 4 if/when v4 ships) are not handled here; that +// belongs in a dedicated package mirroring the openapi2conv pattern. +// +// doc must be Validate()'d before calling Upgrade; passing an invalid +// document is undefined behaviour. +func Upgrade(doc *openapi3.T, opts ...Option) { + if doc == nil { + return + } + + o := upgradeOptions{} + for _, apply := range opts { + apply(&o) + } + + w := &walker{ + visited: map[*openapi3.Schema]struct{}{}, + opts: o, + } + w.walkDoc(doc) + + if doc.OpenAPI != latestTargetVersion { + w.logf("openapi: %s -> %s", doc.OpenAPI, latestTargetVersion) + doc.OpenAPI = latestTargetVersion + } +} + +// UpgradeSchema canonicalizes a single schema (and its descendants) in place. +// Exposed for callers that need to upgrade a sub-tree rather than a full +// document — e.g., a diff tool comparing isolated schemas. +func UpgradeSchema(s *openapi3.Schema) { + if s == nil { + return + } + w := &walker{visited: map[*openapi3.Schema]struct{}{}} + w.walkSchema(s) +} + +// walker carries cycle-tracking state and verbose output across the schema +// graph. Each *Schema is visited at most once. +type walker struct { + visited map[*openapi3.Schema]struct{} + opts upgradeOptions +} + +func (w *walker) logf(format string, args ...any) { + if w.opts.verbose == nil { + return + } + fmt.Fprintf(w.opts.verbose, format, args...) + fmt.Fprintln(w.opts.verbose) +} + +// walkDoc visits every Schema reachable from the document root. +func (w *walker) walkDoc(doc *openapi3.T) { + if doc.Components != nil { + for _, sr := range doc.Components.Schemas { + w.walkSchemaRef(sr) + } + for _, pr := range doc.Components.Parameters { + if pr != nil && pr.Value != nil { + w.walkSchemaRef(pr.Value.Schema) + for _, mt := range pr.Value.Content { + w.walkMediaType(mt) + } + } + } + for _, hr := range doc.Components.Headers { + if hr != nil && hr.Value != nil { + w.walkSchemaRef(hr.Value.Schema) + for _, mt := range hr.Value.Content { + w.walkMediaType(mt) + } + } + } + for _, rb := range doc.Components.RequestBodies { + if rb != nil && rb.Value != nil { + for _, mt := range rb.Value.Content { + w.walkMediaType(mt) + } + } + } + for _, rr := range doc.Components.Responses { + if rr != nil && rr.Value != nil { + for _, mt := range rr.Value.Content { + w.walkMediaType(mt) + } + for _, hr := range rr.Value.Headers { + if hr != nil && hr.Value != nil { + w.walkSchemaRef(hr.Value.Schema) + } + } + } + } + } + + for _, pathItem := range doc.Paths.Map() { + w.walkPathItem(pathItem) + } + + for _, pathItem := range doc.Webhooks { + w.walkPathItem(pathItem) + } +} + +func (w *walker) walkPathItem(pathItem *openapi3.PathItem) { + if pathItem == nil { + return + } + for _, pr := range pathItem.Parameters { + if pr != nil && pr.Value != nil { + w.walkSchemaRef(pr.Value.Schema) + } + } + for _, op := range pathItem.Operations() { + w.walkOperation(op) + } +} + +func (w *walker) walkOperation(op *openapi3.Operation) { + if op == nil { + return + } + for _, pr := range op.Parameters { + if pr != nil && pr.Value != nil { + w.walkSchemaRef(pr.Value.Schema) + for _, mt := range pr.Value.Content { + w.walkMediaType(mt) + } + } + } + if op.RequestBody != nil && op.RequestBody.Value != nil { + for _, mt := range op.RequestBody.Value.Content { + w.walkMediaType(mt) + } + } + if op.Responses != nil { + for _, rr := range op.Responses.Map() { + if rr == nil || rr.Value == nil { + continue + } + for _, mt := range rr.Value.Content { + w.walkMediaType(mt) + } + for _, hr := range rr.Value.Headers { + if hr != nil && hr.Value != nil { + w.walkSchemaRef(hr.Value.Schema) + } + } + } + } + for _, cb := range op.Callbacks { + if cb == nil || cb.Value == nil { + continue + } + for _, pathItem := range cb.Value.Map() { + w.walkPathItem(pathItem) + } + } +} + +func (w *walker) walkMediaType(mt *openapi3.MediaType) { + if mt == nil { + return + } + w.walkSchemaRef(mt.Schema) +} + +func (w *walker) walkSchemaRef(sr *openapi3.SchemaRef) { + if sr == nil || sr.Value == nil { + return + } + w.walkSchema(sr.Value) +} + +func (w *walker) walkSchema(s *openapi3.Schema) { + if s == nil { + return + } + if _, seen := w.visited[s]; seen { + return + } + w.visited[s] = struct{}{} + + // Apply the rewrites at this node before descending — order doesn't + // matter, the transformations are independent. + w.rewriteNullable(s) + w.rewriteExclusiveBounds(s) + w.rewriteExample(s) + + // Recurse into every child schema. + for _, sub := range s.Properties { + w.walkSchemaRef(sub) + } + w.walkSchemaRef(s.Items) + if s.AdditionalProperties.Schema != nil { + w.walkSchemaRef(s.AdditionalProperties.Schema) + } + for _, sub := range s.AllOf { + w.walkSchemaRef(sub) + } + for _, sub := range s.OneOf { + w.walkSchemaRef(sub) + } + for _, sub := range s.AnyOf { + w.walkSchemaRef(sub) + } + w.walkSchemaRef(s.Not) + for _, sub := range s.PatternProperties { + w.walkSchemaRef(sub) + } +} + +// rewriteNullable converts `nullable: true` into a type-array form. +// +// - With a non-empty Type: append "null" (deduped). +// - Without a Type: drop nullable; the spec then accepts any type (no Type +// restriction), which subsumes null. This matches the OAI guide's silence +// on the "no type" edge case while keeping the rewrite lossless. +func (w *walker) rewriteNullable(s *openapi3.Schema) { + if !s.Nullable { + return + } + if s.Type != nil && len(*s.Type) > 0 { + if !slices.Contains(*s.Type, openapi3.TypeNull) { + newTypes := append(*s.Type, openapi3.TypeNull) + s.Type = &newTypes + } + } + w.logf("nullable: true -> dropped (Types=%v)", s.Type) + s.Nullable = false +} + +// rewriteExclusiveBounds converts the 3.0 boolean modifier into the 3.1 +// numeric form: +// +// minimum: x, exclusiveMinimum: true -> exclusiveMinimum: x (Min cleared) +// exclusiveMinimum: false -> field dropped (default) +// +// Mirror logic for maximum. +func (w *walker) rewriteExclusiveBounds(s *openapi3.Schema) { + // Lower bound. + if s.ExclusiveMin.Bool != nil { + if *s.ExclusiveMin.Bool && s.Min != nil { + v := *s.Min + s.ExclusiveMin = openapi3.ExclusiveBound{Value: &v} + s.Min = nil + w.logf("exclusiveMinimum: true + minimum: %v -> exclusiveMinimum: %v (numeric)", v, v) + } else { + // false, or true without paired minimum — either way, the + // boolean form has no meaning in 3.1. Drop it. + s.ExclusiveMin = openapi3.ExclusiveBound{} + w.logf("exclusiveMinimum: -> dropped") + } + } + + // Upper bound. + if s.ExclusiveMax.Bool != nil { + if *s.ExclusiveMax.Bool && s.Max != nil { + v := *s.Max + s.ExclusiveMax = openapi3.ExclusiveBound{Value: &v} + s.Max = nil + w.logf("exclusiveMaximum: true + maximum: %v -> exclusiveMaximum: %v (numeric)", v, v) + } else { + s.ExclusiveMax = openapi3.ExclusiveBound{} + w.logf("exclusiveMaximum: -> dropped") + } + } +} + +// rewriteExample converts the singular `example` field into the plural +// `examples` array on Schema Objects. The plural form is required in 3.1. +// +// Note: `example`/`examples` on Parameter and MediaType objects use a +// different shape (a map of named Example refs in 3.0+ that did not change +// for 3.1) and are not touched here. +func (w *walker) rewriteExample(s *openapi3.Schema) { + if s.Example == nil { + return + } + // Preserve the existing examples array; append to it. Some 3.0 specs + // do set both example and examples (the latter as a vendor-flavored + // extension); we keep both. + s.Examples = append(s.Examples, s.Example) + s.Example = nil + w.logf("example: -> examples: [..., ]") +} diff --git a/openapi3conv/openapi3_conv_test.go b/openapi3conv/openapi3_conv_test.go new file mode 100644 index 000000000..98f64f527 --- /dev/null +++ b/openapi3conv/openapi3_conv_test.go @@ -0,0 +1,474 @@ +package openapi3conv_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3conv" +) + +func loadV30(t *testing.T, raw string) *openapi3.T { + t.Helper() + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(raw)) + require.NoError(t, err) + return doc +} + +// requireValidate asserts doc passes openapi3 validation. Used as the +// before-and-after invariant around Upgrade calls: a document valid as +// 3.0 must remain valid after canonicalization to the latest 3.x. +func requireValidate(t *testing.T, doc *openapi3.T, when string) { + t.Helper() + require.NoError(t, doc.Validate(context.Background()), + "document must validate %s Upgrade", when) +} + +// upgradeAndAssertValid runs Upgrade with a Validate invariant on both +// sides — input must validate, output must validate. Tests that exercise +// representational rewrites use this helper so a regression that +// produces an invalid output document fails the test. +func upgradeAndAssertValid(t *testing.T, doc *openapi3.T, opts ...openapi3conv.Option) { + t.Helper() + requireValidate(t, doc, "before") + openapi3conv.Upgrade(doc, opts...) + requireValidate(t, doc, "after") +} + +// Round-trips through JSON so the result reflects what tooling consumers see +// (omitted zero-value fields, normalized ordering). Easier to compare against +// expected output than walking structs. +func marshalJSON(t *testing.T, doc *openapi3.T) map[string]any { + t.Helper() + b, err := json.Marshal(doc) + require.NoError(t, err) + var m map[string]any + require.NoError(t, json.Unmarshal(b, &m)) + return m +} + +// --------------------------------------------------------------------------- +// Version bump +// --------------------------------------------------------------------------- + +func TestUpgrade_BumpsVersion(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +`) + upgradeAndAssertValid(t, doc) + assert.Equal(t, "3.2.0", doc.OpenAPI) +} + +// 3.2 is purely additive over 3.1 — no schema-level rewrites between them. +// Upgrade applies the 3.0 → 3.1 rewrites (still required) and writes the +// 3.2 version string. The result is canonical and version-correct. +func TestUpgrade_RewritesAppliedAlongsideVersionBump(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: string + nullable: true +`) + upgradeAndAssertValid(t, doc) + assert.Equal(t, "3.2.0", doc.OpenAPI) + assert.Equal(t, openapi3.Types{"string", "null"}, *doc.Components.Schemas["Pet"].Value.Type) +} + +// --------------------------------------------------------------------------- +// Nullable rewrite +// --------------------------------------------------------------------------- + +func TestUpgrade_NullableWithType(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: string + nullable: true +`) + upgradeAndAssertValid(t, doc) + pet := doc.Components.Schemas["Pet"].Value + require.NotNil(t, pet.Type) + assert.Equal(t, openapi3.Types{"string", "null"}, *pet.Type) + assert.False(t, pet.Nullable, "nullable should be cleared after rewrite") +} + +func TestUpgrade_NullableAlreadyHasNullInTypeArray(t *testing.T) { + // Input intentionally mixes a 3.0 version stamp with a 3.1-only + // type array containing "null" — it doesn't validate as 3.0, so + // skip the before-Upgrade validate invariant here. + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: ['string', 'null'] + nullable: true +`) + openapi3conv.Upgrade(doc) + pet := doc.Components.Schemas["Pet"].Value + assert.Equal(t, openapi3.Types{"string", "null"}, *pet.Type, "no duplicate null appended") + assert.False(t, pet.Nullable) +} + +func TestUpgrade_NullableNoType(t *testing.T) { + // nullable without an accompanying type is ambiguous in 3.0 (the spec is + // silent on whether nullable applies). Drop nullable; the schema then + // accepts any type, which subsumes null. + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + nullable: true +`) + upgradeAndAssertValid(t, doc) + pet := doc.Components.Schemas["Pet"].Value + assert.False(t, pet.Nullable) + assert.Nil(t, pet.Type) +} + +func TestUpgrade_NullableInsideProperties(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: object + properties: + name: + type: string + nullable: true + ageInYears: + type: integer + nullable: true +`) + upgradeAndAssertValid(t, doc) + props := doc.Components.Schemas["Pet"].Value.Properties + assert.Equal(t, openapi3.Types{"string", "null"}, *props["name"].Value.Type) + assert.Equal(t, openapi3.Types{"integer", "null"}, *props["ageInYears"].Value.Type) +} + +// --------------------------------------------------------------------------- +// Exclusive-bound rewrite +// --------------------------------------------------------------------------- + +func TestUpgrade_ExclusiveMinTrueWithMinimum(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Score: + type: integer + minimum: 5 + exclusiveMinimum: true +`) + upgradeAndAssertValid(t, doc) + score := doc.Components.Schemas["Score"].Value + assert.Nil(t, score.Min, "Min cleared") + require.NotNil(t, score.ExclusiveMin.Value) + assert.Equal(t, 5.0, *score.ExclusiveMin.Value) + assert.Nil(t, score.ExclusiveMin.Bool) +} + +func TestUpgrade_ExclusiveMaxTrueWithMaximum(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Score: + type: integer + maximum: 100 + exclusiveMaximum: true +`) + upgradeAndAssertValid(t, doc) + score := doc.Components.Schemas["Score"].Value + assert.Nil(t, score.Max) + require.NotNil(t, score.ExclusiveMax.Value) + assert.Equal(t, 100.0, *score.ExclusiveMax.Value) + assert.Nil(t, score.ExclusiveMax.Bool) +} + +func TestUpgrade_ExclusiveMinFalseDropped(t *testing.T) { + // `exclusiveMinimum: false` is the default — it carries no information. + // The merged 3.1 form drops it. + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Score: + type: integer + minimum: 5 + exclusiveMinimum: false +`) + upgradeAndAssertValid(t, doc) + score := doc.Components.Schemas["Score"].Value + require.NotNil(t, score.Min) + assert.Equal(t, 5.0, *score.Min) + assert.False(t, score.ExclusiveMin.IsSet(), "exclusiveMinimum: false should be dropped") +} + +func TestUpgrade_ExclusiveBoundsLeavesNumericIntact(t *testing.T) { + // A document already in 3.1 numeric form should round-trip unchanged + // (apart from the version bump to 3.2.0). + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(` +openapi: 3.1.1 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Score: + type: integer + exclusiveMinimum: 5 +`)) + require.NoError(t, err) + + upgradeAndAssertValid(t, doc) + score := doc.Components.Schemas["Score"].Value + require.NotNil(t, score.ExclusiveMin.Value) + assert.Equal(t, 5.0, *score.ExclusiveMin.Value) + assert.Nil(t, score.Min) +} + +// --------------------------------------------------------------------------- +// Example -> Examples rewrite +// --------------------------------------------------------------------------- + +func TestUpgrade_ExampleToExamples(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: string + example: fido +`) + upgradeAndAssertValid(t, doc) + pet := doc.Components.Schemas["Pet"].Value + assert.Nil(t, pet.Example) + require.Len(t, pet.Examples, 1) + assert.Equal(t, "fido", pet.Examples[0]) +} + +func TestUpgrade_ExampleAppendsToExistingExamples(t *testing.T) { + // Input intentionally mixes a 3.0 version stamp with the 3.1-only + // schema-level examples array — it doesn't validate as 3.0, so + // skip the before-Upgrade validate invariant here. + loader := openapi3.NewLoader() + doc, err := loader.LoadFromData([]byte(` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: string + example: fido + examples: [rex] +`)) + require.NoError(t, err) + openapi3conv.Upgrade(doc) + pet := doc.Components.Schemas["Pet"].Value + assert.Nil(t, pet.Example) + assert.Equal(t, []any{"rex", "fido"}, pet.Examples) +} + +// --------------------------------------------------------------------------- +// Idempotence +// --------------------------------------------------------------------------- + +func TestUpgrade_Idempotent(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: object + properties: + name: + type: string + nullable: true + example: fido + score: + type: integer + minimum: 0 + exclusiveMinimum: true +`) + upgradeAndAssertValid(t, doc) + first := marshalJSON(t, doc) + + upgradeAndAssertValid(t, doc) + second := marshalJSON(t, doc) + + assert.Equal(t, first, second, "second pass must be a no-op") +} + +// --------------------------------------------------------------------------- +// Walks reachable from operations and request/response bodies +// --------------------------------------------------------------------------- + +func TestUpgrade_WalksOperationSchemas(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: + /pets: + get: + parameters: + - name: id + in: query + schema: + type: string + nullable: true + responses: + "200": + description: ok + content: + application/json: + schema: + type: object + properties: + total: + type: integer + minimum: 0 + exclusiveMinimum: true +`) + upgradeAndAssertValid(t, doc) + + getOp := doc.Paths.Value("/pets").Get + param := getOp.Parameters[0].Value.Schema.Value + assert.Equal(t, openapi3.Types{"string", "null"}, *param.Type) + + body := getOp.Responses.Value("200").Value.Content["application/json"].Schema.Value + total := body.Properties["total"].Value + assert.Nil(t, total.Min) + require.NotNil(t, total.ExclusiveMin.Value) + assert.Equal(t, 0.0, *total.ExclusiveMin.Value) +} + +// --------------------------------------------------------------------------- +// Cycle safety +// --------------------------------------------------------------------------- + +func TestUpgrade_CycleSafe(t *testing.T) { + // Hand-build a cycle without going through YAML — the loader resolves + // $ref into shared *Schema pointers, so a self-referential schema + // becomes a true graph cycle. The walker must terminate. + cycle := &openapi3.Schema{Type: &openapi3.Types{"object"}} + cycle.Properties = openapi3.Schemas{ + "self": &openapi3.SchemaRef{Value: cycle}, + "name": &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + } + doc := &openapi3.T{ + OpenAPI: "3.0.3", + Info: &openapi3.Info{Title: "t", Version: "1"}, + Paths: openapi3.NewPaths(), + Components: &openapi3.Components{ + Schemas: openapi3.Schemas{"Cycle": &openapi3.SchemaRef{Value: cycle}}, + }, + } + + done := make(chan struct{}) + go func() { openapi3conv.Upgrade(doc); close(done) }() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("Upgrade hung on a cyclic schema") + } + + // Sanity: the rewrite still ran on the non-cyclic property. + name := cycle.Properties["name"].Value + assert.Equal(t, openapi3.Types{"string", "null"}, *name.Type) +} + +// --------------------------------------------------------------------------- +// Verbose logging +// --------------------------------------------------------------------------- + +func TestUpgrade_VerboseLogsRewrites(t *testing.T) { + doc := loadV30(t, ` +openapi: 3.0.3 +info: {title: t, version: '1'} +paths: {} +components: + schemas: + Pet: + type: string + nullable: true + example: fido +`) + var buf bytes.Buffer + upgradeAndAssertValid(t, doc, openapi3conv.WithWriter(&buf)) + out := buf.String() + assert.Contains(t, out, "openapi: 3.0.3 -> 3.2.0") + assert.Contains(t, out, "nullable") + assert.Contains(t, out, "example") +} + +// --------------------------------------------------------------------------- +// Nil safety +// --------------------------------------------------------------------------- + +func TestUpgrade_NilDocDoesNotPanic(t *testing.T) { + // Per Pierre's guidance, Upgrade no longer validates input — doc + // must be Validate()'d first. Nil is the one case we still defend + // against, returning silently rather than panicking. + openapi3conv.Upgrade(nil) +} + +func TestUpgradeSchema_NilSchema(t *testing.T) { + // Should not panic. + openapi3conv.UpgradeSchema(nil) +} + +func TestUpgradeSchema_OperatesOnSubtree(t *testing.T) { + s := &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "x": &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Nullable: true, + }}, + }, + } + openapi3conv.UpgradeSchema(s) + x := s.Properties["x"].Value + assert.Equal(t, openapi3.Types{"string", "null"}, *x.Type) + assert.False(t, x.Nullable) +} diff --git a/openapi3filter/csv_file_upload_test.go b/openapi3filter/csv_file_upload_test.go index 89efb96d9..4a7a573c7 100644 --- a/openapi3filter/csv_file_upload_test.go +++ b/openapi3filter/csv_file_upload_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "io" "mime/multipart" "net/http" @@ -107,7 +106,7 @@ baz,qux,quux`, require.NoError(t, err) if err = openapi3filter.ValidateRequestBody( - context.Background(), + req.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/errors.go b/openapi3filter/errors.go index ea7c7c312..1039bed51 100644 --- a/openapi3filter/errors.go +++ b/openapi3filter/errors.go @@ -33,9 +33,8 @@ func (err *RequestError) Error() string { return fmt.Sprintf("parameter %q in %s has an error: %s", v.Name, v.In, reason) } else if v := err.RequestBody; v != nil { return fmt.Sprintf("request body has an error: %s", reason) - } else { - return reason } + return reason } func (err RequestError) Unwrap() error { diff --git a/openapi3filter/issue1045_test.go b/openapi3filter/issue1045_test.go index 048dbd374..bffd686dc 100644 --- a/openapi3filter/issue1045_test.go +++ b/openapi3filter/issue1045_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -126,12 +127,12 @@ components: route, pathParams, err := router.FindRoute(req) require.NoError(t, err) - validationInput := &RequestValidationInput{ + validationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, } - err = ValidateRequest(loader.Context, validationInput) + err = openapi3filter.ValidateRequest(loader.Context, validationInput) if testcase.shouldFail { require.Error(t, err, "This test case should fail "+testcase.data) } else { diff --git a/openapi3filter/issue1110_test.go b/openapi3filter/issue1110_test.go index a3dfb475f..d4254f5d1 100644 --- a/openapi3filter/issue1110_test.go +++ b/openapi3filter/issue1110_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -85,12 +86,12 @@ paths: route, pathParams, err := router.FindRoute(req) require.NoError(t, err) - validationInput := &RequestValidationInput{ + validationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, } - err = ValidateRequest(ctx, validationInput) + err = openapi3filter.ValidateRequest(ctx, validationInput) if testcase.shouldFail { require.Error(t, err, "This test should fail: "+testcase.name) } else { diff --git a/openapi3filter/issue201_test.go b/openapi3filter/issue201_test.go index ec0b2a1f1..7e25f1167 100644 --- a/openapi3filter/issue201_test.go +++ b/openapi3filter/issue201_test.go @@ -1,7 +1,6 @@ package openapi3filter import ( - "context" "io" "net/http" "strings" @@ -122,7 +121,7 @@ paths: route, pathParams, err := router.FindRoute(r) require.NoError(t, err) - err = ValidateResponse(context.Background(), &ResponseValidationInput{ + err = ValidateResponse(t.Context(), &ResponseValidationInput{ RequestValidationInput: &RequestValidationInput{ Request: r, PathParams: pathParams, diff --git a/openapi3filter/issue267_test.go b/openapi3filter/issue267_test.go index 44e0cab10..f78967051 100644 --- a/openapi3filter/issue267_test.go +++ b/openapi3filter/issue267_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -280,12 +281,12 @@ components: route, pathParams, err := router.FindRoute(req) require.NoError(t, err) - validationInput := &RequestValidationInput{ + validationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, } - err = ValidateRequest(loader.Context, validationInput) + err = openapi3filter.ValidateRequest(loader.Context, validationInput) if testcase.shouldFail { require.Error(t, err, "This test case should fail "+testcase.data) } else { diff --git a/openapi3filter/issue436_test.go b/openapi3filter/issue436_test.go index fa106c5a1..0986d86f7 100644 --- a/openapi3filter/issue436_test.go +++ b/openapi3filter/issue436_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "io" "mime/multipart" "net/http" @@ -121,7 +120,7 @@ components: } if err = openapi3filter.ValidateRequestBody( - context.Background(), + req.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/issue624_test.go b/openapi3filter/issue624_test.go index 1fdbdea34..c98693caf 100644 --- a/openapi3filter/issue624_test.go +++ b/openapi3filter/issue624_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -57,12 +58,12 @@ paths: route, pathParams, err := router.FindRoute(httpReq) require.NoError(t, err) - requestValidationInput := &RequestValidationInput{ + requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, } - err = ValidateRequest(ctx, requestValidationInput) + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) require.NoError(t, err) }) } diff --git a/openapi3filter/issue639_test.go b/openapi3filter/issue639_test.go index 136967953..b212f7b9c 100644 --- a/openapi3filter/issue639_test.go +++ b/openapi3filter/issue639_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "encoding/json" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -51,12 +52,12 @@ func TestIssue639(t *testing.T) { tests := []struct { name string - options *Options + options *openapi3filter.Options expectedDefaultVal any }{ { name: "no defaults are added to requests", - options: &Options{ + options: &openapi3filter.Options{ SkipSettingDefaults: true, }, expectedDefaultVal: nil, @@ -79,13 +80,13 @@ func TestIssue639(t *testing.T) { route, pathParams, err := router.FindRoute(httpReq) require.NoError(t, err) - requestValidationInput := &RequestValidationInput{ + requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, Options: testcase.options, } - err = ValidateRequest(ctx, requestValidationInput) + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) require.NoError(t, err) bodyAfterValidation, err := io.ReadAll(httpReq.Body) require.NoError(t, err) diff --git a/openapi3filter/issue689_test.go b/openapi3filter/issue689_test.go index 592d53f74..4efbec30a 100644 --- a/openapi3filter/issue689_test.go +++ b/openapi3filter/issue689_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "io" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -63,7 +64,7 @@ func TestIssue689(t *testing.T) { tests := []struct { name string - options *Options + options *openapi3filter.Options body string method string checkErr require.ErrorAssertionFunc @@ -79,7 +80,7 @@ func TestIssue689(t *testing.T) { name: "non read-only property is added to request when validation disabled", body: `{"testNoReadOnly": true}`, method: http.MethodPut, - options: &Options{ + options: &openapi3filter.Options{ ExcludeReadOnlyValidations: true, }, checkErr: require.NoError, @@ -94,7 +95,7 @@ func TestIssue689(t *testing.T) { name: "read-only property is added to requests when validation disabled", body: `{"testWithReadOnly": true}`, method: http.MethodPut, - options: &Options{ + options: &openapi3filter.Options{ ExcludeReadOnlyValidations: true, }, checkErr: require.NoError, @@ -110,7 +111,7 @@ func TestIssue689(t *testing.T) { name: "non write-only property is added to request when validation disabled", body: `{"testNoWriteOnly": true}`, method: http.MethodGet, - options: &Options{ + options: &openapi3filter.Options{ ExcludeWriteOnlyValidations: true, }, checkErr: require.NoError, @@ -125,7 +126,7 @@ func TestIssue689(t *testing.T) { name: "write-only property is added to requests when validation disabled", body: `{"testWithWriteOnly": true}`, method: http.MethodGet, - options: &Options{ + options: &openapi3filter.Options{ ExcludeWriteOnlyValidations: true, }, checkErr: require.NoError, @@ -142,7 +143,7 @@ func TestIssue689(t *testing.T) { route, pathParams, err := router.FindRoute(httpReq) require.NoError(t, err) - requestValidationInput := &RequestValidationInput{ + requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, @@ -150,17 +151,17 @@ func TestIssue689(t *testing.T) { } if test.method == http.MethodGet { - responseValidationInput := &ResponseValidationInput{ + responseValidationInput := &openapi3filter.ResponseValidationInput{ RequestValidationInput: requestValidationInput, Status: 200, Header: httpReq.Header, Body: io.NopCloser(strings.NewReader(test.body)), Options: test.options, } - err = ValidateResponse(ctx, responseValidationInput) + err = openapi3filter.ValidateResponse(ctx, responseValidationInput) } else { - err = ValidateRequest(ctx, requestValidationInput) + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) } test.checkErr(t, err) }) diff --git a/openapi3filter/issue707_test.go b/openapi3filter/issue707_test.go index a7cbc39ed..884efda21 100644 --- a/openapi3filter/issue707_test.go +++ b/openapi3filter/issue707_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -48,12 +49,12 @@ paths: tests := []struct { name string - options *Options + options *openapi3filter.Options expectedQuery string }{ { name: "no defaults are added to requests parameters", - options: &Options{ + options: &openapi3filter.Options{ SkipSettingDefaults: true, }, expectedQuery: "", @@ -73,13 +74,13 @@ paths: route, pathParams, err := router.FindRoute(httpReq) require.NoError(t, err) - requestValidationInput := &RequestValidationInput{ + requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, Options: testcase.options, } - err = ValidateRequest(ctx, requestValidationInput) + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) require.NoError(t, err) require.NoError(t, err) diff --git a/openapi3filter/issue722_test.go b/openapi3filter/issue722_test.go index 73648e0fe..9c250c109 100644 --- a/openapi3filter/issue722_test.go +++ b/openapi3filter/issue722_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "io" "mime/multipart" "net/http" @@ -120,7 +119,7 @@ components: } if err = openapi3filter.ValidateRequestBody( - context.Background(), + t.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/issue733_test.go b/openapi3filter/issue733_test.go index f43a826e8..933402dfb 100644 --- a/openapi3filter/issue733_test.go +++ b/openapi3filter/issue733_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "encoding/json" "math" "math/big" @@ -65,7 +64,7 @@ paths: require.NoError(t, err) err = openapi3filter.ValidateRequest( - context.Background(), + t.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/issue743_test.go b/openapi3filter/issue743_test.go index 24122e104..fe437be67 100644 --- a/openapi3filter/issue743_test.go +++ b/openapi3filter/issue743_test.go @@ -9,11 +9,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestValidateRequestWithAnAuthenticatorFunc_CanConsumeTheRequestBody(t *testing.T) { @@ -63,7 +64,7 @@ security: require.NoError(t, err) err = openapi3filter.ValidateRequest( - context.Background(), + t.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/issue884_test.go b/openapi3filter/issue884_test.go index ea6e461c0..62f6fdcda 100644 --- a/openapi3filter/issue884_test.go +++ b/openapi3filter/issue884_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "net/http" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -67,12 +68,12 @@ func TestIssue884(t *testing.T) { tests := []struct { name string - options *Options + options *openapi3filter.Options expectedQuery url.Values }{ { name: "no defaults are added to requests", - options: &Options{ + options: &openapi3filter.Options{ SkipSettingDefaults: true, }, expectedQuery: url.Values{}, @@ -97,13 +98,13 @@ func TestIssue884(t *testing.T) { route, pathParams, err := router.FindRoute(httpReq) require.NoError(t, err) - requestValidationInput := &RequestValidationInput{ + requestValidationInput := &openapi3filter.RequestValidationInput{ Request: httpReq, PathParams: pathParams, Route: route, Options: testcase.options, } - err = ValidateRequest(ctx, requestValidationInput) + err = openapi3filter.ValidateRequest(ctx, requestValidationInput) require.NoError(t, err) q := httpReq.URL.Query() diff --git a/openapi3filter/issue949_test.go b/openapi3filter/issue949_test.go index f87fae378..6845dc8b5 100644 --- a/openapi3filter/issue949_test.go +++ b/openapi3filter/issue949_test.go @@ -12,10 +12,11 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" - "github.com/stretchr/testify/require" ) const testSchema = ` @@ -60,7 +61,7 @@ func TestIssue949(t *testing.T) { doc, err := loader.LoadFromData([]byte(testSchema)) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.NoError(t, err) router, err := gorillamux.NewRouter(doc) diff --git a/openapi3filter/issue991_test.go b/openapi3filter/issue991_test.go index 10737ea93..317ea133e 100644 --- a/openapi3filter/issue991_test.go +++ b/openapi3filter/issue991_test.go @@ -1,7 +1,6 @@ package openapi3filter import ( - "context" "net/http" "testing" @@ -128,7 +127,7 @@ func TestValidateRequestDefault(t *testing.T) { PathParams: pathParams, Route: route, } - err = ValidateRequest(context.Background(), validationInput) + err = ValidateRequest(t.Context(), validationInput) assert.IsType(t, tc.expectedErr, err, "ValidateRequest(): error = %v, expectedError %v", err, tc.expectedErr) if tc.expectedErr != nil { return diff --git a/openapi3filter/middleware_test.go b/openapi3filter/middleware_test.go index d492bec0c..197481a10 100644 --- a/openapi3filter/middleware_test.go +++ b/openapi3filter/middleware_test.go @@ -144,21 +144,21 @@ func (h *validatorTestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) w.Header().Set("Content-Type", h.contentType) if h.errStatusCode != 0 { w.WriteHeader(h.errStatusCode) - w.Write([]byte(h.errBody)) + _, _ = w.Write([]byte(h.errBody)) return } if !testUrlRE.MatchString(r.URL.Path) { w.WriteHeader(http.StatusNotFound) - w.Write([]byte(h.errBody)) + _, _ = w.Write([]byte(h.errBody)) return } switch r.Method { case "GET": w.WriteHeader(http.StatusOK) - w.Write([]byte(h.getBody)) + _, _ = w.Write([]byte(h.getBody)) case "POST": w.WriteHeader(http.StatusCreated) - w.Write([]byte(h.postBody)) + _, _ = w.Write([]byte(h.postBody)) default: http.Error(w, h.errBody, http.StatusMethodNotAllowed) } @@ -494,7 +494,7 @@ paths: // Customize validation error responses to use JSON w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - json.NewEncoder(w).Encode(map[string]any{ + _ = json.NewEncoder(w).Encode(map[string]any{ "status": status, "message": http.StatusText(status), }) diff --git a/openapi3filter/options_test.go b/openapi3filter/options_test.go index fd19329ff..f0e320c62 100644 --- a/openapi3filter/options_test.go +++ b/openapi3filter/options_test.go @@ -1,7 +1,6 @@ package openapi3filter_test import ( - "context" "fmt" "net/http" "strings" @@ -74,7 +73,7 @@ paths: Route: route, Options: opts, } - err = openapi3filter.ValidateRequest(context.Background(), validationInput) + err = openapi3filter.ValidateRequest(req.Context(), validationInput) fmt.Println(err.Error()) diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go index 3e71a8735..2c9517ca7 100644 --- a/openapi3filter/req_resp_decoder.go +++ b/openapi3filter/req_resp_decoder.go @@ -8,12 +8,14 @@ import ( "errors" "fmt" "io" + "maps" "mime" "mime/multipart" "net/http" "net/url" "reflect" "regexp" + "slices" "strconv" "strings" @@ -539,14 +541,14 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM } values = strings.Split(values[0], delim) } - val, err := d.parseArray(values, sm, schema) + val, err := d.parseArray(values, schema) return val, ok, err } // parseArray returns an array that contains items from a raw array. // Every item is parsed as a primitive value. // The function returns an error when an error happened while parse array's items. -func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]any, error) { +func (d *urlValuesDecoder) parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]any, error) { var value []any for i, v := range raw { @@ -1057,9 +1059,7 @@ func buildFromSchemas(schemas openapi3.SchemaRefs, params map[string]any, mapKey if err == nil && val != nil { if m, ok := val.(map[string]any); ok { - for k, v := range m { - resultMap[k] = v - } + maps.Copy(resultMap, m) continue } @@ -1343,7 +1343,13 @@ func UrlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3. if !schema.Value.Type.Is("object") { return nil, errors.New("unsupported schema of request body") } - for propName, propSchema := range schema.Value.Properties { + propNames := make([]string, 0, len(schema.Value.Properties)) + for name := range schema.Value.Properties { + propNames = append(propNames, name) + } + slices.Sort(propNames) + for _, propName := range propNames { + propSchema := schema.Value.Properties[propName] propType := propSchema.Value.Type switch { case propType.Is("object"): @@ -1393,7 +1399,13 @@ func decodeSchemaConstructs(dec *urlValuesDecoder, schemas []*openapi3.SchemaRef return err } - for name, prop := range schemaRef.Value.Properties { + propNames := make([]string, 0, len(schemaRef.Value.Properties)) + for name := range schemaRef.Value.Properties { + propNames = append(propNames, name) + } + slices.Sort(propNames) + for _, name := range propNames { + prop := schemaRef.Value.Properties[name] value, present, err := decodeProperty(dec, name, prop, encFn) if err != nil || !present { continue @@ -1499,7 +1511,7 @@ func MultipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S return nil, fmt.Errorf("part %s: %w", name, err) } - // Parse primitive types when no content type is explicitely provided, or the content type is set to text/plain + // Parse primitive types when no content type is explicitly provided, or the content type is set to text/plain if contentType := partHeader.Get(headerCT); contentType == "" || contentType == "text/plain" { if value, err = parsePrimitive(value.(string), valueSchema); err != nil { if v, ok := err.(*ParseError); ok { @@ -1515,23 +1527,15 @@ func MultipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S allTheProperties := make(map[string]*openapi3.SchemaRef) if len(schema.Value.AllOf) > 0 { for _, sr := range schema.Value.AllOf { - for k, v := range sr.Value.Properties { - allTheProperties[k] = v - } + maps.Copy(allTheProperties, sr.Value.Properties) if addProps := sr.Value.AdditionalProperties.Schema; addProps != nil { - for k, v := range addProps.Value.Properties { - allTheProperties[k] = v - } + maps.Copy(allTheProperties, addProps.Value.Properties) } } } else { - for k, v := range schema.Value.Properties { - allTheProperties[k] = v - } + maps.Copy(allTheProperties, schema.Value.Properties) if addProps := schema.Value.AdditionalProperties.Schema; addProps != nil { - for k, v := range addProps.Value.Properties { - allTheProperties[k] = v - } + maps.Copy(allTheProperties, addProps.Value.Properties) } } diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go index fe33a15b2..04c53a473 100644 --- a/openapi3filter/req_resp_decoder_test.go +++ b/openapi3filter/req_resp_decoder_test.go @@ -2,7 +2,6 @@ package openapi3filter import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -82,14 +81,6 @@ var ( }, }, } - anyofSchemaObject = &openapi3.SchemaRef{ - Value: &openapi3.Schema{ - AnyOf: []*openapi3.SchemaRef{ - objectOneRSchema, - objectTwoRSchema, - }, - }, - } stringArraySchema = arrayOf(stringSchema) integerArraySchema = arrayOf(integerSchema) objectSchema = objectOf("id", stringSchema, "name", stringSchema) @@ -1560,7 +1551,7 @@ func TestDecodeParameter(t *testing.T) { if tc.query != "" { query := req.URL.Query() - for _, param := range strings.Split(tc.query, "&") { + for param := range strings.SplitSeq(tc.query, "&") { v := strings.Split(param, "=") query.Add(v[0], v[1]) } @@ -1594,7 +1585,7 @@ func TestDecodeParameter(t *testing.T) { Responses: openapi3.NewResponses(openapi3.WithStatus(200, &openapi3.ResponseRef{Value: openapi3.NewResponse().WithDescription("OK")})), } doc.AddOperation(path, http.MethodGet, op) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(doc) require.NoError(t, err) @@ -1683,16 +1674,19 @@ func TestDecodeBody(t *testing.T) { {name: "f", contentType: "application/json", data: strings.NewReader(`{"foo1": "foo1"}`), filename: "f1"}, {name: "f", contentType: "application/pdf", data: strings.NewReader("foo2"), filename: "f2"}, }) + require.NoError(t, err) multipartBinaryEncodingCTUnsupported, multipartMimeBinaryEncodingCTUnsupported, err := newTestMultipartForm([]*testFormPart{ {name: "b", contentType: "application/json", data: strings.NewReader(`{"bar1": "bar1"}`), filename: "b1"}, {name: "d", contentType: "application/pdf", data: strings.NewReader("doo1"), filename: "d1"}, }) + require.NoError(t, err) multipartBinaryEncodingCTNotMatching, multipartMimeBinaryEncodingCTNotMatching, err := newTestMultipartForm([]*testFormPart{ {name: "b", contentType: "application/json", data: strings.NewReader(`{"bar1": "bar1"}`), filename: "b1"}, {name: "d", contentType: "application/pdf", data: strings.NewReader("doo1"), filename: "d1"}, }) + require.NoError(t, err) testCases := []struct { name string @@ -1955,8 +1949,7 @@ func newTestMultipartForm(parts []*testFormPart) (io.Reader, string, error) { } func TestRegisterAndUnregisterBodyDecoder(t *testing.T) { - var decoder BodyDecoder - decoder = func(body io.Reader, h http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (decoded any, err error) { + var decoder BodyDecoder = func(body io.Reader, h http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (decoded any, err error) { var data []byte if data, err = io.ReadAll(body); err != nil { return diff --git a/openapi3filter/req_resp_encoder_test.go b/openapi3filter/req_resp_encoder_test.go index 7942dab93..86ff65918 100644 --- a/openapi3filter/req_resp_encoder_test.go +++ b/openapi3filter/req_resp_encoder_test.go @@ -9,8 +9,7 @@ import ( ) func TestRegisterAndUnregisterBodyEncoder(t *testing.T) { - var encoder BodyEncoder - encoder = func(body any) (data []byte, err error) { + var encoder BodyEncoder = func(body any) (data []byte, err error) { return []byte(strings.Join(body.([]string), ",")), nil } const contentType = "text/csv" diff --git a/openapi3filter/testdata/issue1100_test.go b/openapi3filter/testdata/issue1100_test.go index b5147dfe8..fdd490a3f 100644 --- a/openapi3filter/testdata/issue1100_test.go +++ b/openapi3filter/testdata/issue1100_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" - "github.com/getkin/kin-openapi/openapi3filter" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) diff --git a/openapi3filter/upload_arbitrary_file_test.go b/openapi3filter/upload_arbitrary_file_test.go index f0d771f04..fa7d185f5 100644 --- a/openapi3filter/upload_arbitrary_file_test.go +++ b/openapi3filter/upload_arbitrary_file_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "io" "mime/multipart" "net/http" @@ -80,8 +79,8 @@ paths: fw, err := writer.CreatePart(h) require.NoError(t, err) - _, err = io.Copy(fw, bytes.NewReader(tt.zipData)) + _, err = io.Copy(fw, bytes.NewReader(tt.zipData)) require.NoError(t, err) } @@ -96,7 +95,7 @@ paths: require.NoError(t, err) if err = openapi3filter.ValidateRequestBody( - context.Background(), + t.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go index 01f6cd14e..e4d944286 100644 --- a/openapi3filter/validate_request.go +++ b/openapi3filter/validate_request.go @@ -234,12 +234,14 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param var opts []openapi3.SchemaValidationOption if options.MultiError { - opts = make([]openapi3.SchemaValidationOption, 0, 1) opts = append(opts, openapi3.MultiErrors()) } if options.customSchemaErrorFunc != nil { opts = append(opts, openapi3.SetSchemaErrorMessageCustomizer(options.customSchemaErrorFunc)) } + if input.Route != nil && input.Route.Spec.IsOpenAPI31OrLater() { + opts = append(opts, openapi3.EnableJSONSchema2020()) + } if err = schema.VisitJSON(value, opts...); err != nil { return &RequestError{Input: input, Parameter: parameter, Err: err} } @@ -330,7 +332,7 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req } defaultsSet := false - opts := make([]openapi3.SchemaValidationOption, 0, 4+len(options.SchemaValidationOptions)) + var opts []openapi3.SchemaValidationOption opts = append(opts, openapi3.VisitAsRequest()) if !options.SkipSettingDefaults { opts = append(opts, openapi3.DefaultsSet(func() { defaultsSet = true })) @@ -349,6 +351,9 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req } // Append additional schema validation options (e.g., document-scoped format validators) opts = append(opts, options.SchemaValidationOptions...) + if input.Route != nil && input.Route.Spec.IsOpenAPI31OrLater() { + opts = append(opts, openapi3.EnableJSONSchema2020()) + } // Validate JSON with the schema if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil { diff --git a/openapi3filter/validate_request_example_test.go b/openapi3filter/validate_request_example_test.go index 653db3039..b795a58a3 100644 --- a/openapi3filter/validate_request_example_test.go +++ b/openapi3filter/validate_request_example_test.go @@ -1,4 +1,4 @@ -package openapi3filter +package openapi3filter_test import ( "context" @@ -6,8 +6,10 @@ import ( "fmt" "log" "net/http" + "slices" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" "github.com/getkin/kin-openapi/routers/gorillamux" ) @@ -49,21 +51,16 @@ paths: "Bob": {"secrets.read", "secrets.write"}, } - authenticationFunc := func(_ context.Context, ai *AuthenticationInput) error { + authenticationFunc := func(_ context.Context, ai *openapi3filter.AuthenticationInput) error { user := ai.RequestValidationInput.Request.Header.Get("X-User") if user == "" { return errUnauthenticated } for _, requiredScope := range ai.Scopes { - var allowed bool - for _, scope := range userScopes[user] { - if scope == requiredScope { - allowed = true - break - } - } - if !allowed { + if slices.Contains(userScopes[user], requiredScope) { + break + } else { return errForbidden } } @@ -78,15 +75,15 @@ paths: validateRequest := func(req *http.Request) { route, pathParams, _ := router.FindRoute(req) - validationInput := &RequestValidationInput{ + validationInput := &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, - Options: &Options{ + Options: &openapi3filter.Options{ AuthenticationFunc: authenticationFunc, }, } - err := ValidateRequest(context.TODO(), validationInput) + err := openapi3filter.ValidateRequest(context.TODO(), validationInput) switch { case errors.Is(err, errUnauthenticated): fmt.Println("username is required") diff --git a/openapi3filter/validate_request_test.go b/openapi3filter/validate_request_test.go index 3d0c9956b..0f257fe3a 100644 --- a/openapi3filter/validate_request_test.go +++ b/openapi3filter/validate_request_test.go @@ -201,7 +201,7 @@ components: AuthenticationFunc: verifyAPIKeyPresence, }, } - err = ValidateRequest(context.Background(), validationInput) + err = ValidateRequest(t.Context(), validationInput) assert.IsType(t, tc.expectedErr, err, "ValidateRequest(): error = %v, expectedError %v", err, tc.expectedErr) if tc.expectedErr != nil { return @@ -446,17 +446,18 @@ func TestValidateQueryParams(t *testing.T) { Responses: openapi3.NewResponses(openapi3.WithStatus(200, &openapi3.ResponseRef{Value: openapi3.NewResponse().WithDescription("OK")})), } doc.AddOperation("/test", http.MethodGet, op) - err := doc.Validate(context.Background()) + err := doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(doc) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://test.org/test?"+tc.query, nil) + require.NoError(t, err) route, pathParams, err := router.FindRoute(req) require.NoError(t, err) input := &RequestValidationInput{Request: req, PathParams: pathParams, Route: route} - err = ValidateParameter(context.Background(), input, tc.param) + err = ValidateParameter(t.Context(), input, tc.param) if tc.err != nil { require.Error(t, err) @@ -475,10 +476,10 @@ func TestValidateQueryParams(t *testing.T) { return } - require.NoError(t, err) got, _, err := decodeStyledParameter(tc.param, input) + require.NoError(t, err) require.EqualValues(t, tc.want, got) }) } @@ -547,7 +548,7 @@ paths: route, pathParams, err := router.FindRoute(req) require.NoError(t, err) - err = ValidateRequest(context.Background(), &RequestValidationInput{ + err = ValidateRequest(t.Context(), &RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, @@ -557,7 +558,7 @@ paths: }) require.NoError(t, err) - err = ValidateRequest(context.Background(), &RequestValidationInput{ + err = ValidateRequest(t.Context(), &RequestValidationInput{ Request: req, PathParams: pathParams, Route: route, diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go index 7ab72493e..d055c7173 100644 --- a/openapi3filter/validate_response.go +++ b/openapi3filter/validate_response.go @@ -20,9 +20,7 @@ import ( // Note: One can tune the behavior of uniqueItems: true verification // by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error { - req := input.RequestValidationInput.Request - switch req.Method { - case "HEAD": + if req := input.RequestValidationInput.Request; req.Method == http.MethodHead { return nil } status := input.Status @@ -63,7 +61,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error return &ResponseError{Input: input, Reason: "response has not been resolved"} } - opts := make([]openapi3.SchemaValidationOption, 0, 3+len(options.SchemaValidationOptions)) + var opts []openapi3.SchemaValidationOption if options.MultiError { opts = append(opts, openapi3.MultiErrors()) } @@ -75,6 +73,9 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error } // Append additional schema validation options (e.g., document-scoped format validators) opts = append(opts, options.SchemaValidationOptions...) + if route.Spec.IsOpenAPI31OrLater() { + opts = append(opts, openapi3.EnableJSONSchema2020()) + } headers := make([]string, 0, len(response.Headers)) for k := range response.Headers { diff --git a/openapi3filter/validation_error_test.go b/openapi3filter/validation_error_test.go index 8a1ff8262..fdc40e513 100644 --- a/openapi3filter/validation_error_test.go +++ b/openapi3filter/validation_error_test.go @@ -756,7 +756,8 @@ func runTest_Middleware(t *testing.T, handler http.Handler, encoder ErrorEncoder func TestValidationHandler_ServeHTTP(t *testing.T) { t.Run("errors on invalid requests", func(t *testing.T) { - httpCtx := context.WithValue(context.Background(), "pig", "tails") + type pig struct{} + httpCtx := context.WithValue(t.Context(), pig{}, "tails") r, err := http.NewRequest(http.MethodGet, "http://unknown-host.com/v2/pet", nil) require.NoError(t, err) r = r.WithContext(httpCtx) @@ -798,7 +799,8 @@ func TestValidationHandler_ServeHTTP(t *testing.T) { func TestValidationHandler_Middleware(t *testing.T) { t.Run("errors on invalid requests", func(t *testing.T) { - httpCtx := context.WithValue(context.Background(), "pig", "tails") + type pig struct{} + httpCtx := context.WithValue(t.Context(), pig{}, "tails") r, err := http.NewRequest(http.MethodGet, "http://unknown-host.com/v2/pet", nil) require.NoError(t, err) r = r.WithContext(httpCtx) diff --git a/openapi3filter/validation_kit.go b/openapi3filter/validation_kit.go index 9e11e4fc8..16951e476 100644 --- a/openapi3filter/validation_kit.go +++ b/openapi3filter/validation_kit.go @@ -81,5 +81,5 @@ func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { code = sc.StatusCode() } w.WriteHeader(code) - w.Write(body) + _, _ = w.Write(body) } diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go index c6cec7195..1ac7ec4c2 100644 --- a/openapi3filter/validation_test.go +++ b/openapi3filter/validation_test.go @@ -156,10 +156,11 @@ func TestFilter(t *testing.T) { ), } - err := doc.Validate(context.Background()) + err := doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(doc) require.NoError(t, err) + expectWithDecoder := func(req ExampleRequest, resp ExampleResponse, decoder ContentParameterDecoder) error { t.Logf("Request: %s %s", req.Method, req.URL) httpReq, err := http.NewRequest(req.Method, req.URL, marshalReader(req.Body)) @@ -177,7 +178,7 @@ func TestFilter(t *testing.T) { Route: route, ParamDecoder: decoder, } - if err := ValidateRequest(context.Background(), requestValidationInput); err != nil { + if err := ValidateRequest(t.Context(), requestValidationInput); err != nil { return err } t.Logf("Response: %d", resp.Status) @@ -195,10 +196,11 @@ func TestFilter(t *testing.T) { require.NoError(t, err) responseValidationInput.SetBodyBytes(data) } - err = ValidateResponse(context.Background(), responseValidationInput) + err = ValidateResponse(t.Context(), responseValidationInput) require.NoError(t, err) return nil } + expect := func(req ExampleRequest, resp ExampleResponse) error { return expectWithDecoder(req, resp, nil) } @@ -431,7 +433,7 @@ func TestValidateRequestBody(t *testing.T) { req.Header.Set(headerCT, tc.mime) } inp := &RequestValidationInput{Request: req} - err := ValidateRequestBody(context.Background(), inp, tc.body) + err := ValidateRequestBody(t.Context(), inp, tc.body) if tc.wantErr == nil { require.NoError(t, err) @@ -563,7 +565,7 @@ func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *test }) } - err := doc.Validate(context.Background()) + err := doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(doc) require.NoError(t, err) @@ -604,7 +606,7 @@ func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *test } // Validate the request - err = ValidateRequest(context.Background(), &req) + err = ValidateRequest(t.Context(), &req) require.NoError(t, err) for securityRequirement, validated := range schemesValidated { @@ -688,7 +690,7 @@ func TestAnySecurityRequirementMet(t *testing.T) { }) } - err := doc.Validate(context.Background()) + err := doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(&doc) require.NoError(t, err) @@ -711,7 +713,7 @@ func TestAnySecurityRequirementMet(t *testing.T) { } // Validate the security requirements - err = ValidateSecurityRequirements(context.Background(), &req, *route.Operation.Security) + err = ValidateSecurityRequirements(t.Context(), &req, *route.Operation.Security) // If there should have been an error if tc.error { @@ -790,7 +792,7 @@ func TestAllSchemesMet(t *testing.T) { }) } - err := doc.Validate(context.Background()) + err := doc.Validate(t.Context()) require.NoError(t, err) router, err := legacyrouter.NewRouter(&doc) require.NoError(t, err) @@ -813,7 +815,7 @@ func TestAllSchemesMet(t *testing.T) { } // Validate the security requirements - err = ValidateSecurityRequirements(context.Background(), &req, *route.Operation.Security) + err = ValidateSecurityRequirements(t.Context(), &req, *route.Operation.Security) // If there should have been an error if tc.error { diff --git a/openapi3filter/zip_file_upload_test.go b/openapi3filter/zip_file_upload_test.go index a61804062..4e82ad483 100644 --- a/openapi3filter/zip_file_upload_test.go +++ b/openapi3filter/zip_file_upload_test.go @@ -2,7 +2,6 @@ package openapi3filter_test import ( "bytes" - "context" "io" "mime/multipart" "net/http" @@ -104,7 +103,7 @@ paths: require.NoError(t, err) if err = openapi3filter.ValidateRequestBody( - context.Background(), + t.Context(), &openapi3filter.RequestValidationInput{ Request: req, PathParams: pathParams, diff --git a/openapi3gen/field_info.go b/openapi3gen/field_info.go index 4764198d9..70a148c8c 100644 --- a/openapi3gen/field_info.go +++ b/openapi3gen/field_info.go @@ -30,7 +30,7 @@ func appendFields(fields []theFieldInfo, parentIndex []int, t reflect.Type) []th // For each field numField := t.NumField() iteration: - for i := 0; i < numField; i++ { + for i := range numField { f := t.Field(i) index := make([]int, 0, len(parentIndex)+1) index = append(index, parentIndex...) @@ -78,8 +78,9 @@ iteration: // Parse the tag if jsonTag != "" { field.HasJSONTag = true - for i, part := range strings.Split(jsonTag, ",") { - if i == 0 { + first := true + for part := range strings.SplitSeq(jsonTag, ",") { + if first { if part != "" { field.JSONName = part } @@ -91,6 +92,7 @@ iteration: field.JSONString = true } } + first = false } } diff --git a/openapi3gen/openapi3gen.go b/openapi3gen/openapi3gen.go index 5d9b64abf..e5b9840be 100644 --- a/openapi3gen/openapi3gen.go +++ b/openapi3gen/openapi3gen.go @@ -7,6 +7,7 @@ import ( "math" "reflect" "regexp" + "slices" "strings" "time" @@ -178,7 +179,7 @@ func (g *Generator) generateSchemaRefFor(parents []*theTypeInfo, t reflect.Type, func getStructField(t reflect.Type, fieldInfo theFieldInfo) reflect.StructField { var ff reflect.StructField // fieldInfo.Index is an array of indexes starting from the root of the type - for i := 0; i < len(fieldInfo.Index); i++ { + for i := range len(fieldInfo.Index) { ff = t.Field(fieldInfo.Index[i]) t = ff.Type for t.Kind() == reflect.Ptr { @@ -190,10 +191,8 @@ func getStructField(t reflect.Type, fieldInfo theFieldInfo) reflect.StructField func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) { typeInfo := getTypeInfo(t) - for _, parent := range parents { - if parent == typeInfo { - return nil, &CycleError{} - } + if slices.Contains(parents, typeInfo) { + return nil, &CycleError{} } isRoot := cap(parents) == 0 if isRoot { @@ -335,7 +334,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type if _, ok := g.componentSchemaRefs[typeName]; ok && g.opts.exportComponentSchemas.ExportComponentSchemas { // Check if we have already parsed this component schema ref based on the name of the struct // and use that if so - return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema), nil + return openapi3.NewSchemaRef("#/components/schemas/"+typeName, schema), nil } for _, fieldInfo := range typeInfo.Fields { @@ -436,6 +435,23 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type typeName := g.generateTypeName(t) + // Anonymous types (e.g. `struct{...}` literals) have an empty name. + // Registering them as a component would produce + // "#/components/schemas/" which violates the OpenAPI spec + // (component keys must match ^[a-zA-Z0-9._-]+$). Inline the + // schema instead so downstream codegen tools don't choke. + if typeName == "" { + return openapi3.NewSchemaRef("", schema), nil + } + + // The body becomes the canonical component definition shared by every + // $ref site. The Nullable flag, set above when the type was reached + // via *T, applies to one specific field, not to the type itself -- + // keeping it here would emit a polluted component like + // `"Foo": {"nullable": true, "type": "object", ...}` and break codegen + // tools (e.g. Orval generates `interface Foo {...} | null`). + schema.Nullable = false + g.componentSchemaRefs[typeName] = struct{}{} return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema), nil } @@ -473,7 +489,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche } g.componentSchemaRefs[typeName] = struct{}{} - return openapi3.NewSchemaRef(fmt.Sprintf("#/components/schemas/%s", typeName), schema) + return openapi3.NewSchemaRef("#/components/schemas/"+typeName, schema) } var RefSchemaRef = openapi3.NewSchemaRef("Ref", diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go index e18d8619c..183cb7960 100644 --- a/openapi3gen/openapi3gen_test.go +++ b/openapi3gen/openapi3gen_test.go @@ -4,13 +4,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/stretchr/testify/assert" "reflect" "strconv" "strings" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/getkin/kin-openapi/openapi3" @@ -214,7 +214,7 @@ func TestExportedNonTagged(t *testing.T) { type Bla struct { A string Another string `json:"another"` - yetAnother string // unused because unexported + yetAnother string //nolint:unused // unused because unexported EvenAYaml string `yaml:"even_a_yaml"` } @@ -433,7 +433,7 @@ func ExampleSchemaCustomizer() { schema.Max = &maxVal } if tag.Get("myenumtag") != "" { - for _, s := range strings.Split(tag.Get("myenumtag"), ",") { + for s := range strings.SplitSeq(tag.Get("myenumtag"), ",") { schema.Enum = append(schema.Enum, s) } } @@ -667,3 +667,67 @@ func TestExportComponentSchemasForTimeProp(t *testing.T) { return !strings.Contains(string(schema), "#/components/schemas/Time") }, "Expected no schema for time.Time property but got one: %s", schema) } + +// TestExportComponentSchemasNoNullableOnBody verifies that a struct reached +// via *T (e.g. as the Data field of `Response[*Channel]`) does not pollute +// its exported component definition with `nullable: true`. The component +// body is shared by every reference site, so a nullable component breaks +// codegen tools (e.g. Orval emits `interface Channel {...} | null`). +func TestExportComponentSchemasNoNullableOnBody(t *testing.T) { + type Channel struct { + ID int `json:"id"` + Name string `json:"name"` + } + type Wrapper struct { + Data *Channel `json:"data"` + } + + schemas := make(openapi3.Schemas) + g := openapi3gen.NewGenerator( + openapi3gen.UseAllExportedFields(), + openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{ + ExportComponentSchemas: true, + }), + ) + + _, err := g.NewSchemaRefForValue(&Wrapper{}, schemas) + require.NoError(t, err) + + channel, ok := schemas["Channel"] + require.True(t, ok, "Channel must be registered as a component") + require.NotNil(t, channel.Value) + assert.False(t, channel.Value.Nullable, "exported component body must not carry the nullable flag from a *T reference site") +} + +// TestExportComponentSchemasSkipsAnonymousType verifies that anonymous struct +// types (whose reflect.Type.Name() is "") are inlined rather than registered +// as components, so the resulting spec has no "#/components/schemas/" entry +// or ref (which would violate the OpenAPI component-key pattern). +func TestExportComponentSchemasSkipsAnonymousType(t *testing.T) { + type Outer struct { + Inline struct { + X int + } + } + + schemas := make(openapi3.Schemas) + g := openapi3gen.NewGenerator( + openapi3gen.UseAllExportedFields(), + openapi3gen.CreateComponentSchemas(openapi3gen.ExportComponentSchemasOptions{ + ExportComponentSchemas: true, + ExportTopLevelSchema: true, + }), + ) + + _, err := g.NewSchemaRefForValue(&Outer{}, schemas) + require.NoError(t, err) + + require.NotEmpty(t, schemas, "outer named struct should still be registered as a component") + + _, hasEmptyKey := schemas[""] + assert.False(t, hasEmptyKey, "anonymous nested struct should not be registered as a component") + + for key := range schemas { + assert.NotEmpty(t, key, "every component schema must have a non-empty key") + } +} diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go index 77186d704..bf502e6ff 100644 --- a/routers/gorillamux/router.go +++ b/routers/gorillamux/router.go @@ -104,7 +104,7 @@ func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string for i, m := range r.muxes { var match mux.RouteMatch if m.muxRoute.Match(req, &match) { - if err := match.MatchErr; err != nil { + if err := match.MatchErr; err != nil { //nolint:staticcheck // What then? } vars := match.Vars @@ -240,6 +240,7 @@ func permutePart(part0 string, srv *openapi3.Server) []string { for value := range m { s = append(s, value) } + slices.Sort(s) var2val[name] = mapAndSlice{m: m, s: s} } if len(var2val) == 0 { diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go index cd87d8ab5..abe733276 100644 --- a/routers/gorillamux/router_test.go +++ b/routers/gorillamux/router_test.go @@ -298,6 +298,7 @@ func TestServerOverrideAtPathLevel(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "https://another.com/hello", nil) require.NoError(t, err) route, _, err := router.FindRoute(req) + require.NoError(t, err) require.Equal(t, "/hello", route.Path) req, err = http.NewRequest(http.MethodGet, "https://example.com/hello", nil) diff --git a/routers/issue356_test.go b/routers/issue356_test.go index 100e0183d..1c3528393 100644 --- a/routers/issue356_test.go +++ b/routers/issue356_test.go @@ -1,7 +1,6 @@ package routers_test import ( - "context" "io" "net/http" "net/http/httptest" @@ -62,11 +61,11 @@ paths: ``: true, } { - loader := &openapi3.Loader{Context: context.Background()} + loader := &openapi3.Loader{Context: t.Context()} t.Logf("using servers: %q (%v)", servers, expectError) doc, err := loader.LoadFromData(spec(servers)) require.NoError(t, err) - err = doc.Validate(context.Background()) + err = doc.Validate(t.Context()) require.NoError(t, err) gorillamuxNewRouterWrapped := func(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) { return gorillamux.NewRouter(doc) @@ -95,7 +94,7 @@ paths: PathParams: pathParams, Route: route, } - err = openapi3filter.ValidateRequest(context.Background(), requestValidationInput) + err = openapi3filter.ValidateRequest(t.Context(), requestValidationInput) require.NoError(t, err) } @@ -105,7 +104,8 @@ paths: route, pathParams, err := router.FindRoute(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + _, err := w.Write([]byte(err.Error())) + require.NoError(t, err) return } @@ -118,7 +118,8 @@ paths: require.NoError(t, err) w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{}")) + _, err = w.Write([]byte("{}")) + require.NoError(t, err) })) defer ts.Close() diff --git a/routers/legacy/pathpattern/node_test.go b/routers/legacy/pathpattern/node_test.go index 14d8457ce..99366f76d 100644 --- a/routers/legacy/pathpattern/node_test.go +++ b/routers/legacy/pathpattern/node_test.go @@ -1,12 +1,14 @@ -package pathpattern +package pathpattern_test import ( "testing" + + "github.com/getkin/kin-openapi/routers/legacy/pathpattern" ) func TestPatterns(t *testing.T) { - DefaultOptions.SupportRegExp = true - rootNode := &Node{} + pathpattern.DefaultOptions.SupportRegExp = true + rootNode := &pathpattern.Node{} add := func(path, value string) { rootNode.MustAdd(path, value, nil) } @@ -22,8 +24,8 @@ func TestPatterns(t *testing.T) { add("/root/{path*}", "DIRECTORY") add("/impossible_route", "IMPOSSIBLE") - add(PathFromHost("www.nike.com", true), "WWW-HOST") - add(PathFromHost("{other}.nike.com", true), "OTHER-HOST") + add(pathpattern.PathFromHost("www.nike.com", true), "WWW-HOST") + add(pathpattern.PathFromHost("{other}.nike.com", true), "OTHER-HOST") expect := func(uri string, expected string, expectedArgs ...string) { actually := "not found" @@ -63,9 +65,9 @@ func TestPatterns(t *testing.T) { expect("/root/", "DIRECTORY", "") expect("/root/a/b/c", "DIRECTORY", "a/b/c") - expect(PathFromHost("www.nike.com", true), "WWW-HOST") - expect(PathFromHost("example.nike.com", true), "OTHER-HOST", "example") - expect(PathFromHost("subdomain.example.nike.com", true), "not found") + expect(pathpattern.PathFromHost("www.nike.com", true), "WWW-HOST") + expect(pathpattern.PathFromHost("example.nike.com", true), "OTHER-HOST", "example") + expect(pathpattern.PathFromHost("subdomain.example.nike.com", true), "not found") } func argsEqual(a, b []string) bool { diff --git a/routers/legacy/router_test.go b/routers/legacy/router_test.go index a3183b78b..708e2338f 100644 --- a/routers/legacy/router_test.go +++ b/routers/legacy/router_test.go @@ -1,4 +1,4 @@ -package legacy +package legacy_test import ( "context" @@ -10,6 +10,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/legacy" ) func TestRouter(t *testing.T) { @@ -129,7 +130,7 @@ func TestRouter(t *testing.T) { err := doc.Validate(context.Background()) require.NoError(t, err) - r, err := NewRouter(doc) + r, err := legacy.NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/not_existing", nil, nil) @@ -169,7 +170,7 @@ func TestRouter(t *testing.T) { } err = doc.Validate(context.Background()) require.NoError(t, err) - r, err = NewRouter(doc) + r, err = legacy.NewRouter(doc) require.NoError(t, err) expect(r, http.MethodGet, "/hello", nil, nil) expect(r, http.MethodGet, "/api/v1/hello", nil, nil) @@ -209,8 +210,10 @@ func TestRouter(t *testing.T) { }) err = doc.Validate(context.Background()) require.Error(t, err) - r, err = NewRouter(doc) + r, err = legacy.NewRouter(doc) require.Error(t, err) - r, err = NewRouter(doc, openapi3.DisableExamplesValidation()) + require.Nil(t, r) + r, err = legacy.NewRouter(doc, openapi3.DisableExamplesValidation()) require.NoError(t, err) + require.NotNil(t, r) }