update(conduit): refactoring open_api to openapi

This commit is contained in:
Patrick Stewart 2024-08-04 06:05:44 -07:00
parent a87f5cedc8
commit 56df9f60fb
9 changed files with 253404 additions and 6 deletions

View file

@ -10,9 +10,42 @@
import 'package:protevus_typeforge/codable.dart';
import 'package:meta/meta.dart';
/// Represents an API object with support for custom extensions.
///
/// This class extends [Coding] and provides functionality to handle
/// custom extensions in API objects. Extensions are key-value pairs
/// where keys must start with "x-".
///
/// The [extensions] map stores all custom extension data.
///
/// When decoding, it automatically extracts and stores all extension fields.
/// When encoding, it validates that all extension keys start with "x-" and
/// includes them in the encoded output.
class APIObject extends Coding {
/// A map to store custom extension data for the API object.
///
/// The keys in this map represent extension names, which must start with "x-".
/// The values can be of any type (dynamic) to accommodate various extension data.
///
/// This map is used to store and retrieve custom extensions that are not part of
/// the standard API object properties. It allows for flexibility in adding
/// custom data to API objects without modifying the core structure.
Map<String, dynamic> extensions = {};
/// Decodes the API object from a [KeyedArchive].
///
/// This method overrides the [decode] method from the superclass and adds
/// functionality to handle custom extensions.
///
/// It performs the following steps:
/// 1. Calls the superclass's decode method to handle standard fields.
/// 2. Identifies all keys in the [object] that start with "x-" as extension keys.
/// 3. For each extension key, decodes its value and stores it in the [extensions] map.
///
/// This allows the APIObject to capture and store any custom extensions
/// present in the decoded data, making them accessible via the [extensions] property.
///
/// [object]: The [KeyedArchive] containing the encoded data to be decoded.
@mustCallSuper
@override
void decode(KeyedArchive object) {
@ -24,6 +57,23 @@ class APIObject extends Coding {
}
}
/// Encodes the API object into a [KeyedArchive].
///
/// This method overrides the [encode] method from the superclass and adds
/// functionality to handle custom extensions.
///
/// It performs the following steps:
/// 1. Validates that all keys in the [extensions] map start with "x-".
/// If any invalid keys are found, it throws an [ArgumentError] with details.
/// 2. Encodes each key-value pair from the [extensions] map into the [object].
///
/// This ensures that all custom extensions are properly encoded and that
/// the extension naming convention (starting with "x-") is enforced.
///
/// Throws:
/// [ArgumentError]: If any extension key does not start with "x-".
///
/// [object]: The [KeyedArchive] where the encoded data will be stored.
@override
@mustCallSuper
void encode(KeyedArchive object) {

View file

@ -13,37 +13,254 @@ import 'package:protevus_openapi/object.dart';
import 'package:protevus_openapi/util.dart';
import 'package:protevus_openapi/v2.dart';
/// Represents an OpenAPI 2.0 specification.
/// Represents an OpenAPI 2.0 specification document.
///
/// This class encapsulates the structure and content of an OpenAPI 2.0 (formerly known as Swagger) specification.
/// It provides methods for creating, parsing, and serializing OpenAPI documents.
///
/// Key features:
/// - Supports creation of empty documents or parsing from JSON/YAML maps.
/// - Implements the OpenAPI 2.0 structure, including info, paths, definitions, etc.
/// - Provides serialization to and deserialization from map representations.
/// - Includes type casting rules for proper data handling.
///
/// Usage:
/// - Create an empty document: `var doc = APIDocument();`
/// - Parse from a map: `var doc = APIDocument.fromMap(jsonMap);`
/// - Serialize to a map: `var map = doc.asMap();`
///
/// This class is part of the Protevus Platform and adheres to the OpenAPI 2.0 specification.
class APIDocument extends APIObject {
/// Creates an empty specification.
/// Creates an empty APIDocument instance.
///
/// This constructor initializes a new APIDocument object with default values
/// for all its properties. It can be used as a starting point for building
/// an OpenAPI 2.0 specification programmatically.
APIDocument();
/// Creates a specification from decoded JSON or YAML document object.
/// Creates an APIDocument instance from a decoded JSON or YAML document object.
///
/// This constructor takes a Map<String, dynamic> representation of an OpenAPI 2.0
/// specification and initializes an APIDocument object with its contents.
///
/// The method uses KeyedArchive.unarchive to convert the map into a KeyedArchive,
/// allowing references within the document. It then calls the decode method to
/// populate the APIDocument instance with the data from the KeyedArchive.
///
/// @param map A Map<String, dynamic> containing the decoded JSON or YAML data
/// of an OpenAPI 2.0 specification.
APIDocument.fromMap(Map<String, dynamic> map) {
decode(KeyedArchive.unarchive(map, allowReferences: true));
}
/// The OpenAPI Specification version that this document adheres to.
///
/// This field is required and should always be set to "2.0" for OpenAPI 2.0
/// (Swagger) specifications. It indicates that this APIDocument instance
/// represents an OpenAPI 2.0 specification.
String version = "2.0";
/// The metadata about the API.
///
/// This property contains information such as the API title, description,
/// version, and other relevant metadata. It is represented by an instance
/// of the APIInfo class.
///
/// The field is nullable, but initialized with a default APIInfo instance.
APIInfo? info = APIInfo();
/// The host (name or IP) serving the API.
///
/// This optional field represents the host (name or IP) serving the API.
/// It must include the host name only and should not include the scheme
/// or sub-paths. It may include a port. If not specified, the host serving
/// the documentation is assumed to be the same as the host serving the API.
/// The value MAY be null to indicate that the host is not yet known.
String? host;
/// The base path on which the API is served, relative to the host.
///
/// This optional field represents the base path for all API operations.
/// If specified, it must start with a forward slash ("/"). If not specified,
/// the API is served directly under the host. The value MAY be null to
/// indicate that the base path is not yet known or is the root ("/").
String? basePath;
/// A list of tags used by the specification with additional metadata.
///
/// The order of the tags can be used to reflect on their order by the parsing tools.
/// Not all tags that are used by the [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operationObject)
/// must be declared. The tags that are not declared may be organized randomly or
/// based on the tools' logic. Each tag name in the list MUST be unique.
///
/// This field is nullable and initialized as an empty list.
List<APITag?>? tags = [];
/// The transfer protocol(s) used by the API.
///
/// This list specifies the transfer protocol(s) that the API supports.
/// Common values include "http", "https", "ws" (WebSocket), and "wss" (secure WebSocket).
/// The order of the protocols does not matter.
///
/// If the schemes is not included, the default scheme to be used is the one used to access
/// the OpenAPI definition itself.
///
/// This field is nullable and initialized as an empty list.
List<String>? schemes = [];
/// The MIME types that the API can consume.
///
/// This list specifies the MIME types of the request payloads that the API can process.
/// Common values might include "application/json", "application/xml", "application/x-www-form-urlencoded", etc.
///
/// If this field is not specified, it is assumed that the API can consume any MIME type.
///
/// This field is nullable and initialized as an empty list.
List<String>? consumes = [];
/// The MIME types that the API can produce.
///
/// This list specifies the MIME types of the response payloads that the API can generate.
/// Common values might include "application/json", "application/xml", "text/plain", etc.
///
/// If this field is not specified, it is assumed that the API can produce any MIME type.
///
/// This field is nullable and initialized as an empty list.
List<String>? produces = [];
/// A list of security requirements for the API.
///
/// Each item in this list is a map representing a security requirement.
/// The keys of these maps are the names of security schemes (as defined in [securityDefinitions]),
/// and their values are lists of scopes required for that scheme.
///
/// An empty list means no security is required.
/// Multiple items in the list represent AND conditions, while multiple entries in a single map represent OR conditions.
///
/// Example:
/// [
/// {"api_key": []},
/// {"oauth2": ["write:pets", "read:pets"]}
/// ]
/// This would require either an API key OR OAuth2 with both write:pets and read:pets scopes.
///
/// This field is nullable and initialized as an empty list.
List<Map<String, List<String?>>?>? security = [];
/// A map of API paths, where each key is a path string and the value is an APIPath object.
///
/// This property represents all the paths available in the API, including their operations,
/// parameters, and responses. Each path is a relative path to an individual endpoint.
/// The path is appended to the basePath in order to construct the full URL.
///
/// The map is nullable and initialized as an empty map. Each APIPath object in the map
/// is also nullable, allowing for flexible path definitions.
///
/// Example:
/// {
/// "/pets": APIPath(...),
/// "/users/{userId}": APIPath(...),
/// }
Map<String, APIPath?>? paths = {};
/// A map of reusable responses that can be used across operations.
///
/// This property defines response objects that can be referenced by multiple
/// operations in the API. Each key in the map is a name for the response,
/// and the corresponding value is an APIResponse object describing the response.
///
/// These responses can be referenced using the '$ref' keyword in operation
/// responses, allowing for reuse and consistency across the API specification.
///
/// The map is nullable, and each APIResponse object within it is also nullable,
/// providing flexibility in defining and referencing responses.
///
/// Example:
/// {
/// "NotFound": APIResponse(...),
/// "InvalidInput": APIResponse(...),
/// }
Map<String, APIResponse?>? responses = {};
/// A map of reusable parameters that can be referenced from operations.
///
/// This property defines parameter objects that can be used across multiple
/// operations in the API. Each key in the map is a unique name for the parameter,
/// and the corresponding value is an APIParameter object describing the parameter.
///
/// These parameters can be referenced using the '$ref' keyword in operation
/// parameters, allowing for reuse and consistency across the API specification.
///
/// The map is nullable, and each APIParameter object within it is also nullable,
/// providing flexibility in defining and referencing parameters.
///
/// Example:
/// {
/// "userId": APIParameter(...),
/// "apiKey": APIParameter(...),
/// }
Map<String, APIParameter?>? parameters = {};
/// A map of reusable schema definitions that can be referenced throughout the API specification.
///
/// This property defines schema objects that can be used to describe complex data structures
/// used in request bodies, response payloads, or as nested properties of other schemas.
/// Each key in the map is a unique name for the schema, and the corresponding value is an
/// APISchemaObject that describes the structure and constraints of the schema.
///
/// These schema definitions can be referenced using the '$ref' keyword in other parts of the
/// API specification, allowing for reuse and simplification of complex data models.
///
/// The map is nullable, and each APISchemaObject within it is also nullable, providing
/// flexibility in defining and referencing schemas.
///
/// Example:
/// {
/// "User": APISchemaObject(...),
/// "Error": APISchemaObject(...),
/// }
Map<String, APISchemaObject?>? definitions = {};
/// A map of security schemes that can be used across the API specification.
///
/// This property defines security scheme objects that can be referenced by the
/// [security] property or an operation's security property. Each key in the map
/// is a unique name for the security scheme, and the corresponding value is an
/// APISecurityScheme object describing the security scheme.
///
/// These security definitions can be used to describe API keys, OAuth2 flows,
/// or other custom security mechanisms required by the API.
///
/// The map is nullable, and each APISecurityScheme object within it is also nullable,
/// providing flexibility in defining and referencing security schemes.
///
/// Example:
/// {
/// "api_key": APISecurityScheme(...),
/// "oauth2": APISecurityScheme(...),
/// }
Map<String, APISecurityScheme?>? securityDefinitions = {};
/// Converts the APIDocument object to a Map<String, dynamic>.
///
/// This method serializes the current APIDocument instance into a Map
/// representation. It uses the KeyedArchive.archive method to perform
/// the serialization, with the allowReferences parameter set to true.
///
/// @return A Map<String, dynamic> containing the serialized data of the APIDocument.
Map<String, dynamic> asMap() {
return KeyedArchive.archive(this, allowReferences: true);
}
/// Defines the type casting rules for specific properties of the APIDocument class.
///
/// This map provides casting instructions for the following properties:
/// - "schemes": A list of strings
/// - "consumes": A list of strings
/// - "produces": A list of strings
/// - "security": A list of maps, where each map has string keys and list of string values
///
/// These casting rules ensure that the data is properly typed when decoded from JSON or YAML.
@override
Map<String, cast.Cast> get castMap => {
"schemes": const cast.List(cast.string),
@ -53,6 +270,19 @@ class APIDocument extends APIObject {
const cast.List(cast.Map(cast.string, cast.List(cast.string)))
};
/// Decodes the APIDocument object from a KeyedArchive.
///
/// This method populates the properties of the APIDocument instance using
/// data from the provided KeyedArchive object. It decodes various fields
/// such as version, host, basePath, schemes, consumes, produces, security,
/// info, tags, paths, responses, parameters, definitions, and
/// securityDefinitions.
///
/// The method also removes null values from certain list properties and
/// creates instances of related API objects (e.g., APIInfo, APITag, APIPath)
/// as needed.
///
/// @param object The KeyedArchive containing the encoded APIDocument data.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -80,6 +310,21 @@ class APIDocument extends APIObject {
);
}
/// Encodes the APIDocument object into a KeyedArchive.
///
/// This method serializes the properties of the APIDocument instance into
/// the provided KeyedArchive object. It encodes various fields such as
/// version (as "swagger"), host, basePath, schemes, consumes, produces,
/// paths, info, parameters, responses, securityDefinitions, security,
/// tags, and definitions.
///
/// The method uses different encoding techniques based on the property type:
/// - Simple properties are encoded directly.
/// - Object maps are encoded using encodeObjectMap.
/// - Single objects (like info) are encoded using encodeObject.
/// - Lists of objects (like tags) are encoded using encodeObjects.
///
/// @param object The KeyedArchive to encode the APIDocument data into.
@override
void encode(KeyedArchive object) {
super.encode(object);

View file

@ -10,13 +10,51 @@
import 'package:protevus_typeforge/codable.dart';
import 'package:protevus_openapi/v2.dart';
/// Represents a header in the OpenAPI specification.
/// A class representing an API header in the OpenAPI specification.
///
/// This class extends [APIProperty] and provides additional functionality
/// specific to API headers. It includes properties for description and items
/// (for array types), as well as methods for encoding and decoding the header
/// object.
///
/// Properties:
/// - description: A string describing the header.
/// - items: An [APIProperty] object representing the items in an array (only used when type is array).
///
/// The class overrides the [decode] and [encode] methods from [APIProperty]
/// to handle the specific properties of an API header.
class APIHeader extends APIProperty {
/// Default constructor for the APIHeader class.
///
/// Creates a new instance of APIHeader without initializing any properties.
/// Properties can be set after instantiation or through the decode method.
APIHeader();
/// A string that provides a brief description of the header.
///
/// This property can be used to give more context or explanation about
/// the purpose and usage of the header in the API documentation.
String? description;
/// An [APIProperty] object representing the items in an array.
///
/// This property is only used when the [type] is set to [APIType.array].
/// It describes the structure and properties of the individual items
/// within the array. The [items] property can be null if not applicable.
APIProperty? items;
/// Decodes the APIHeader object from a [KeyedArchive].
///
/// This method overrides the [decode] method from [APIProperty] to handle
/// the specific properties of an API header.
///
/// It performs the following operations:
/// 1. Calls the superclass decode method to handle common properties.
/// 2. Decodes the 'description' field from the archive.
/// 3. If the header type is an array, it decodes the 'items' field as an APIProperty.
///
/// Parameters:
/// - object: A [KeyedArchive] containing the encoded header data.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -26,6 +64,18 @@ class APIHeader extends APIProperty {
}
}
/// Encodes the APIHeader object into a [KeyedArchive].
///
/// This method overrides the [encode] method from [APIProperty] to handle
/// the specific properties of an API header.
///
/// It performs the following operations:
/// 1. Calls the superclass encode method to handle common properties.
/// 2. Encodes the 'description' field into the archive.
/// 3. If the header type is an array, it encodes the 'items' field as an APIProperty.
///
/// Parameters:
/// - object: A [KeyedArchive] where the encoded header data will be stored.
@override
void encode(KeyedArchive object) {
super.encode(object);

View file

@ -10,18 +10,87 @@
import 'package:protevus_typeforge/codable.dart';
import 'package:protevus_openapi/object.dart';
/// Represents a metadata for an API in the OpenAPI specification.
/// Represents metadata for an API in the OpenAPI specification.
///
/// This class contains information about the API such as its title, description,
/// version, terms of service, contact information, and license details.
///
/// The [APIInfo] class extends [APIObject] and provides methods to decode from
/// and encode to a [KeyedArchive] object, which is useful for serialization and
/// deserialization of API metadata.
///
/// Properties:
/// - [title]: The title of the API (required).
/// - [description]: A brief description of the API (optional).
/// - [version]: The version of the API (optional).
/// - [termsOfServiceURL]: The URL to the Terms of Service for the API (optional).
/// - [contact]: The contact information for the API (optional).
/// - [license]: The license information for the API (optional).
///
/// The class provides a default constructor [APIInfo()] that initializes all
/// properties with default values. It also overrides [decode] and [encode] methods
/// to handle serialization and deserialization of the API metadata.
class APIInfo extends APIObject {
/// Creates empty metadata for specification.
/// Creates a new instance of APIInfo with default values.
///
/// This constructor initializes an APIInfo object with predefined default
/// values for its properties. These include a default title, description,
/// version, terms of service URL, contact information, and license details.
APIInfo();
/// The title of the API.
///
/// This property represents the name or title of the API as defined in the OpenAPI specification.
/// It is a required field and provides a concise, meaningful name to the API.
String title = "API";
/// A brief description of the API.
///
/// This property provides a more detailed explanation of the API's purpose,
/// functionality, or any other relevant information. It's optional and defaults
/// to "Description" if not specified.
String? description = "Description";
/// The version of the API.
///
/// This property represents the version number of the API as defined in the OpenAPI specification.
/// It is typically a string in the format of "major.minor.patch" (e.g., "1.0.0").
/// The default value is "1.0" if not specified.
String? version = "1.0";
/// The URL to the Terms of Service for the API.
///
/// This property represents the URL where the Terms of Service for the API can be found.
/// It's an optional field in the OpenAPI specification and defaults to an empty string if not specified.
String? termsOfServiceURL = "";
/// The contact information for the API.
///
/// This property contains details about the contact person or organization
/// responsible for the API. It includes information such as name, URL, and email.
/// If not specified, it defaults to an instance of APIContact with default values.
APIContact? contact = APIContact();
/// The license information for the API.
///
/// This property contains details about the license under which the API is provided.
/// It includes information such as the license name and URL.
/// If not specified, it defaults to an instance of APILicense with default values.
APILicense? license = APILicense();
/// Decodes the APIInfo object from a KeyedArchive.
///
/// This method overrides the base decode method to populate the APIInfo
/// properties from a KeyedArchive object. It decodes the following properties:
/// - title: The API title (required, defaults to an empty string if not present)
/// - description: A brief description of the API (optional)
/// - termsOfServiceURL: URL to the terms of service (optional)
/// - contact: Contact information, decoded as an APIContact object (optional)
/// - license: License information, decoded as an APILicense object (optional)
/// - version: The API version (optional)
///
/// The method first calls the superclass decode method, then decodes each
/// specific property of the APIInfo class.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -34,6 +103,19 @@ class APIInfo extends APIObject {
version = object.decode("version");
}
/// Encodes the APIInfo object into a KeyedArchive.
///
/// This method overrides the base encode method to serialize the APIInfo
/// properties into a KeyedArchive object. It encodes the following properties:
/// - title: The API title
/// - description: A brief description of the API
/// - version: The API version
/// - termsOfService: URL to the terms of service
/// - contact: Contact information, encoded as an APIContact object
/// - license: License information, encoded as an APILicense object
///
/// The method first calls the superclass encode method, then encodes each
/// specific property of the APIInfo class.
@override
void encode(KeyedArchive object) {
super.encode(object);
@ -48,9 +130,38 @@ class APIInfo extends APIObject {
}
/// Represents contact information in the OpenAPI specification.
///
/// This class extends [APIObject] and provides properties and methods to
/// handle contact details for an API, including name, URL, and email.
///
/// Properties:
/// - [name]: The name of the contact person or organization.
/// - [url]: The URL pointing to the contact information.
/// - [email]: The email address of the contact person or organization.
///
/// The class provides methods to decode from and encode to a [KeyedArchive] object,
/// which is useful for serialization and deserialization of contact information.
class APIContact extends APIObject {
/// Creates a new instance of APIContact with default values.
///
/// This constructor initializes an APIContact object with predefined default
/// values for its properties. These include a default name, URL, and email address.
APIContact();
/// Decodes the APIContact object from a KeyedArchive.
///
/// This method overrides the base decode method to populate the APIContact
/// properties from a KeyedArchive object. It decodes the following properties:
/// - name: The name of the contact person or organization (defaults to "default" if not present)
/// - url: The URL pointing to the contact information (defaults to "http://localhost" if not present)
/// - email: The email address of the contact person or organization (defaults to "default" if not present)
///
/// The method first calls the superclass decode method, then decodes each
/// specific property of the APIContact class, providing default values if
/// the properties are not present in the KeyedArchive.
///
/// Parameters:
/// object: The KeyedArchive containing the encoded APIContact data.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -60,10 +171,39 @@ class APIContact extends APIObject {
email = object.decode("email") ?? "default";
}
/// The name of the contact person or organization.
///
/// This property represents the name associated with the API contact information.
/// It defaults to "default" if not specified.
String name = "default";
/// The URL pointing to the contact information.
///
/// This property represents the URL associated with the API contact information.
/// It provides a web address where users can find more details about the contact.
/// The default value is "http://localhost" if not specified.
String url = "http://localhost";
/// The email address of the contact person or organization.
///
/// This property represents the email address associated with the API contact information.
/// It provides a means of electronic communication for users or developers who need to
/// reach out regarding the API. The default value is "default" if not specified.
String email = "default";
/// Encodes the APIContact object into a KeyedArchive.
///
/// This method overrides the base encode method to serialize the APIContact
/// properties into a KeyedArchive object. It encodes the following properties:
/// - name: The name of the contact person or organization
/// - url: The URL pointing to the contact information
/// - email: The email address of the contact person or organization
///
/// The method first calls the superclass encode method, then encodes each
/// specific property of the APIContact class.
///
/// Parameters:
/// object: The KeyedArchive to encode the APIContact data into.
@override
void encode(KeyedArchive object) {
super.encode(object);
@ -75,9 +215,36 @@ class APIContact extends APIObject {
}
/// Represents a copyright/open source license in the OpenAPI specification.
///
/// This class extends [APIObject] and provides properties and methods to
/// handle license information for an API, including the license name and URL.
///
/// Properties:
/// - [name]: The name of the license.
/// - [url]: The URL where the license can be viewed.
///
/// The class provides methods to decode from and encode to a [KeyedArchive] object,
/// which is useful for serialization and deserialization of license information.
class APILicense extends APIObject {
/// Creates a new instance of APILicense with default values.
///
/// This constructor initializes an APILicense object with predefined default
/// values for its properties. These include a default name and URL for the license.
APILicense();
/// Decodes the APILicense object from a KeyedArchive.
///
/// This method overrides the base decode method to populate the APILicense
/// properties from a KeyedArchive object. It decodes the following properties:
/// - name: The name of the license (defaults to "default" if not present)
/// - url: The URL where the license can be viewed (defaults to "http://localhost" if not present)
///
/// The method first calls the superclass decode method, then decodes each
/// specific property of the APILicense class, providing default values if
/// the properties are not present in the KeyedArchive.
///
/// Parameters:
/// object: The KeyedArchive containing the encoded APILicense data.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -86,9 +253,32 @@ class APILicense extends APIObject {
url = object.decode("url") ?? "http://localhost";
}
/// The name of the license.
///
/// This property represents the name of the license associated with the API.
/// It provides a short identifier for the license type, such as "MIT", "Apache 2.0", etc.
/// The default value is "default" if not specified.
String name = "default";
/// The URL where the license can be viewed.
///
/// This property represents the URL associated with the API license information.
/// It provides a web address where users can find the full text or details of the license.
/// The default value is "http://localhost" if not specified.
String url = "http://localhost";
/// Encodes the APILicense object into a KeyedArchive.
///
/// This method overrides the base encode method to serialize the APILicense
/// properties into a KeyedArchive object. It encodes the following properties:
/// - name: The name of the license
/// - url: The URL where the license can be viewed
///
/// The method first calls the superclass encode method, then encodes each
/// specific property of the APILicense class.
///
/// Parameters:
/// object: The KeyedArchive to encode the APILicense data into.
@override
void encode(KeyedArchive object) {
super.encode(object);
@ -98,9 +288,37 @@ class APILicense extends APIObject {
}
}
/// Represents a tag in the OpenAPI specification.
///
/// This class extends [APIObject] and provides properties and methods to
/// handle tag information for an API, including the tag name and description.
///
/// Properties:
/// - [name]: The name of the tag.
/// - [description]: A short description of the tag.
///
/// The class provides methods to decode from and encode to a [KeyedArchive] object,
/// which is useful for serialization and deserialization of tag information.
class APITag extends APIObject {
/// Creates a new instance of APITag.
///
/// This constructor initializes an APITag object without setting any default values.
/// The [name] and [description] properties will be null until explicitly set or
/// populated through the [decode] method.
APITag();
/// Decodes the APITag object from a KeyedArchive.
///
/// This method overrides the base decode method to populate the APITag
/// properties from a KeyedArchive object. It decodes the following properties:
/// - name: The name of the tag
/// - description: A short description of the tag
///
/// The method first calls the superclass decode method, then decodes each
/// specific property of the APITag class.
///
/// Parameters:
/// object: The KeyedArchive containing the encoded APITag data.
@override
void decode(KeyedArchive object) {
super.decode(object);
@ -109,9 +327,32 @@ class APITag extends APIObject {
description = object.decode("description");
}
/// The name of the tag.
///
/// This property represents the name of the tag associated with the API.
/// It is used to group operations in the API documentation.
/// The value can be null if not specified.
String? name;
/// A short description of the tag.
///
/// This property provides a brief explanation of the tag's purpose or meaning.
/// It can be used to give more context about how the tag is used in the API.
/// The value can be null if no description is provided.
String? description;
/// Encodes the APITag object into a KeyedArchive.
///
/// This method overrides the base encode method to serialize the APITag
/// properties into a KeyedArchive object. It encodes the following properties:
/// - name: The name of the tag
/// - description: A short description of the tag
///
/// The method first calls the superclass encode method, then encodes each
/// specific property of the APITag class.
///
/// Parameters:
/// object: The KeyedArchive to encode the APITag data into.
@override
void encode(KeyedArchive object) {
super.encode(object);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
// Copyright (c) 2017, joeconway. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:protevus_openapi/v2.dart';
import 'package:test/test.dart';
void main() {
group("kubenrnetes spec", () {
APIDocument? doc;
Map<String, dynamic>? original;
setUpAll(() async {
/// download sample api document if we don't already have it.
final String config = await fetchKubernetesExample();
final file = File(config);
final contents = file.readAsStringSync();
original = json.decode(contents) as Map<String, dynamic>;
doc = APIDocument.fromMap(original!);
});
test("Has all metadata", () {
expect(doc!.version, "2.0");
expect(doc!.info!.title, "Kubernetes");
expect(doc!.info!.version, 'v1.12.0');
expect(doc!.host, isNull);
expect(doc!.basePath, isNull);
expect(doc!.tags, isNull);
expect(doc!.schemes, isNull);
});
test("Confirm top-level objects", () {
expect(original!.containsKey("consumes"), false);
expect(original!.containsKey("produces"), false);
});
test("Has paths", () {
expect(doc!.paths!.length, greaterThan(0));
expect(doc!.paths!.length, original!["paths"].length);
final Map<String, dynamic> originalPaths =
original!["paths"] as Map<String, dynamic>;
doc!.paths!.forEach((k, v) {
expect(originalPaths.keys.contains(k), true);
});
});
test("Sample - Namespace", () {
final namespacePath = doc!.paths!["/api/v1/namespaces"];
final getNamespace = namespacePath!.operations["get"];
expect(getNamespace!.description, contains("of kind Namespace"));
expect(getNamespace.consumes, ["*/*"]);
expect(getNamespace.produces, contains("application/json"));
expect(getNamespace.produces, contains("application/yaml"));
expect(getNamespace.parameters!.length, 8);
expect(
getNamespace.parameters!
.firstWhere((p) => p!.name == "limit")!
.location,
APIParameterLocation.query,
);
expect(
getNamespace.parameters!.firstWhere((p) => p!.name == "limit")!.type,
APIType.integer,
);
expect(getNamespace.responses!.keys, contains("401"));
expect(getNamespace.responses!.keys, contains("200"));
final postNamespace = namespacePath.operations["post"];
expect(postNamespace!.parameters!.length, 1);
expect(postNamespace.parameters!.first!.name, "body");
expect(
postNamespace.parameters!.first!.location,
APIParameterLocation.body,
);
});
test("Sample - Reference", () {
final apiPath = doc!.paths!["/api/"];
final apiPathGet = apiPath!.operations["get"];
final response = apiPathGet!.responses!["200"];
final schema = response!.schema;
expect(schema!.description, contains("APIVersions lists the"));
expect(schema.isRequired, ["versions", "serverAddressByClientCIDRs"]);
expect(
schema.properties!["serverAddressByClientCIDRs"]!.items!
.properties!["clientCIDR"]!.description,
contains("The CIDR"),
);
});
test("Can encode as JSON", () {
expect(json.encode(doc!.asMap()), isA<String>());
});
});
}
Future<String> fetchKubernetesExample() async {
// Spec file is too large for pub, and no other way to remove from pub publish
// than putting in .gitignore. Therefore, this file must be downloaded locally
// to this path, from this path: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
const config = "test/specs/kubernetes.json";
final configFile = File(config);
if (!configFile.existsSync()) {
if (!configFile.parent.existsSync()) {
Directory(configFile.parent.path).createSync(recursive: true);
}
const url =
'https://raw.githubusercontent.com/kubernetes/kubernetes/f091073b0fb4d3a550e7f182eb5465338c8b7cbf/api/openapi-spec/swagger.json';
final request = await HttpClient().getUrl(Uri.parse(url));
final response = await request.close();
await response.pipe(configFile.openWrite());
}
return config;
}

View file

@ -0,0 +1,441 @@
import 'dart:convert';
import 'dart:io';
import 'package:protevus_openapi/v3.dart';
import 'package:test/test.dart';
void main() {
group("Components and resolution", () {
test("Can resolve object against registry", () {
final components = APIComponents();
components.schemas["foo"] = APISchemaObject.string();
final ref = APISchemaObject()
..referenceURI = Uri.parse("/components/schemas/foo");
final orig = components.resolve(ref);
expect(orig, isNotNull);
expect(orig!.type, APIType.string);
expect(ref.type, isNull);
final APISchemaObject? constructed = components
.resolveUri(Uri(path: "/components/schemas/foo")) as APISchemaObject?;
expect(constructed, isNotNull);
expect(constructed!.type, APIType.string);
});
test("Invalid ref uri format throws error", () {
final components = APIComponents();
try {
components.resolve(
APISchemaObject()
..referenceURI = Uri.parse("#/components/schemas/foo"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
try {
components.resolve(
APISchemaObject()..referenceURI = Uri.parse("#/components/schemas"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
try {
components.resolve(
APISchemaObject()..referenceURI = Uri.parse("/components/foobar/foo"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
});
test("Nonexisting component returns null", () {
final components = APIComponents();
expect(
components.resolve(
APISchemaObject()
..referenceURI = Uri.parse("/components/schemas/foo"),
),
isNull,
);
});
test("URIs are paths internally, but fragments when serialized", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"string": {
"type": "string",
},
"container": {"\$ref": "#/components/schemas/string"}
}
}
});
expect(
doc.components!.schemas["container"]!.referenceURI!
.toFilePath(windows: Platform.isWindows),
Uri.parse("/components/schemas/string")
.toFilePath(windows: Platform.isWindows),
);
doc.components!.schemas["other"] = APISchemaObject()
..referenceURI = Uri(path: "/components/schemas/container");
final out = doc.asMap();
expect(
out["components"]["schemas"]["container"][r"$ref"],
"#/components/schemas/string",
);
expect(
out["components"]["schemas"]["other"][r"$ref"],
"#/components/schemas/container",
);
});
});
group("Stripe spec", () {
APIDocument? doc;
Map<String, dynamic>? original;
setUpAll(() async {
final String config = await fetchStripExample();
final file = File(config);
final contents = file.readAsStringSync();
original = json.decode(contents) as Map<String, dynamic>;
if (original != null) {
doc = APIDocument.fromMap(original!);
}
});
test("Emits same document in asMap()", () {
expect(doc!.asMap(), original);
});
test("Has openapi version", () {
expect(doc!.version, "3.0.0");
});
test("Has info", () {
expect(doc!.info.title, "Stripe API");
expect(doc!.info.version, isNotNull);
expect(
doc!.info.description,
"The Stripe REST API. Please see https://stripe.com/docs/api for more details.",
);
expect(
doc!.info.termsOfServiceURL.toString(),
"https://stripe.com/us/terms/",
);
expect(doc!.info.contact!.email, "dev-platform@stripe.com");
expect(doc!.info.contact!.name, "Stripe Dev Platform Team");
expect(doc!.info.contact!.url.toString(), "https://stripe.com");
expect(doc!.info.license, isNull);
});
test("Has servers", () {
expect(doc!.servers, isNotNull);
expect(doc!.servers!.length, 1);
expect(doc!.servers!.first!.url.toString(), "https://api.stripe.com/");
expect(doc!.servers!.first!.description, isNull);
expect(doc!.servers!.first!.variables, isNull);
});
test("Tags", () {
expect(doc!.tags, isNull);
});
group("Paths", () {
test("Sample path 1", () {
expect(doc!.paths, isNotNull);
final p = doc!.paths!["/v1/transfers/{transfer}/reversals/{id}"];
expect(p, isNotNull);
expect(p!.description, isNull);
expect(p.operations.length, 2);
final getOp = p.operations["get"];
final getParams = getOp!.parameters;
final getResponses = getOp.responses;
expect(getOp.description, contains("10 most recent reversals"));
expect(getOp.id, "GetTransfersTransferReversalsId");
expect(getParams!.length, 3);
expect(getParams[0].location, APIParameterLocation.query);
expect(
getParams[0].description,
"Specifies which fields in the response should be expanded.",
);
expect(getParams[0].name, "expand");
expect(getParams[0].isRequired, false);
expect(getParams[0].schema!.type, APIType.array);
expect(getParams[0].schema!.items!.type, APIType.string);
expect(getParams[1].location, APIParameterLocation.path);
expect(getParams[1].name, "id");
expect(getParams[1].isRequired, true);
expect(getParams[1].schema!.type, APIType.string);
expect(getParams[2].location, APIParameterLocation.path);
expect(getParams[2].name, "transfer");
expect(getParams[2].isRequired, true);
expect(getParams[2].schema!.type, APIType.string);
expect(getResponses!.length, 2);
expect(getResponses["200"]!.content!.length, 1);
expect(
getResponses["200"]!
.content!["application/json"]!
.schema!
.referenceURI,
Uri.parse(
Uri.parse("#/components/schemas/transfer_reversal").fragment,
),
);
final resolvedElement = getResponses["200"]!
.content!["application/json"]!
.schema!
.properties!["balance_transaction"]!
.anyOf;
expect(
resolvedElement!.last!.properties!["amount"]!.type,
APIType.integer,
);
});
});
group("Components", () {});
test("Security requirement", () {
expect(doc!.security, isNotNull);
expect(doc!.security!.length, 2);
});
});
group("Schema", () {
test("Can read/emit schema object with additionalProperties=true", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"freeform": {"type": "object", "additionalProperties": true}
}
}
});
expect(
doc.components!.schemas["freeform"]!.additionalPropertyPolicy,
APISchemaAdditionalPropertyPolicy.freeForm,
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]["type"],
"object",
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]
["additionalProperties"],
true,
);
});
test("Can read/emit schema object with additionalProperties={}", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"freeform": {
"type": "object",
"additionalProperties": <String, dynamic>{}
}
}
}
});
expect(
doc.components!.schemas["freeform"]!.additionalPropertyPolicy,
APISchemaAdditionalPropertyPolicy.freeForm,
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]["type"],
"object",
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]
["additionalProperties"],
true,
);
});
});
group("Callbacks", () {});
group("'add' methods", () {
test("'addHeader'", () {
final resp = APIResponse("Response");
// when null
resp.addHeader(
"x",
APIHeader(schema: APISchemaObject.string(format: "initial")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
// add more than one
resp.addHeader(
"y",
APIHeader(schema: APISchemaObject.string(format: "second")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
expect(resp.headers!["y"]!.schema!.format, "second");
// cannot replace
resp.addHeader(
"y",
APIHeader(schema: APISchemaObject.string(format: "third")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
expect(resp.headers!["y"]!.schema!.format, "second");
});
test("'addContent'", () {
final resp = APIResponse("Response");
// when null
resp.addContent("x/a", APISchemaObject.string(format: "initial"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
// add more than one
resp.addContent("y/a", APISchemaObject.string(format: "second"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
expect(resp.content!["y/a"]!.schema!.format, "second");
// joins schema in oneOf if key exists
resp.addContent("y/a", APISchemaObject.string(format: "third"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
expect(resp.content!["y/a"]!.schema!.oneOf!.first!.format, "second");
expect(resp.content!["y/a"]!.schema!.oneOf!.last!.format, "third");
});
test("'addResponse'", () {
final op = APIOperation("op", null);
// when null
op.addResponse(
200,
APIResponse.schema("OK", APISchemaObject.string(format: "initial")),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
// add more than one
op.addResponse(
400,
APIResponse.schema(
"KINDABAD",
APISchemaObject.string(format: "second"),
headers: {
"initial":
APIHeader(schema: APISchemaObject.string(format: "initial"))
},
),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.format,
"second",
);
// join responses when key exists
op.addResponse(
400,
APIResponse.schema(
"REALBAD",
APISchemaObject.string(format: "third"),
headers: {
"second":
APIHeader(schema: APISchemaObject.string(format: "initial"))
},
),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
final r400 = op.responses!["400"]!;
expect(r400.description, contains("KINDABAD"));
expect(r400.description, contains("REALBAD"));
expect(r400.content!["application/json"]!.schema!.oneOf, isNotNull);
expect(r400.headers!["initial"], isNotNull);
expect(r400.headers!["second"], isNotNull);
});
test("'addResponse' guards against null value", () {
final op = APIOperation("op", null);
op.addResponse(
400,
APIResponse.schema(
"KINDABAD",
APISchemaObject.string(format: "second"),
),
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.format,
"second",
);
op.addResponse(
400,
APIResponse.schema(
"REALBAD",
APISchemaObject.string(format: "third"),
),
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.oneOf!
.length,
2,
);
});
});
}
Future<String> fetchStripExample() async {
// Spec file is too large for pub, and no other way to remove from pub publish
// than putting in .gitignore. Therefore, this file must be downloaded locally
// to this path, from this path: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
const config = "test/specs/stripe.json";
final configFile = File(config);
if (!configFile.existsSync()) {
if (!configFile.parent.existsSync()) {
Directory(configFile.parent.path).createSync(recursive: true);
}
const url =
'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json';
final request = await HttpClient().getUrl(Uri.parse(url));
final response = await request.close();
await response.pipe(File(config).openWrite());
}
return config;
}