update(conduit): refactor from common to openapi
This commit is contained in:
parent
64ad47ef08
commit
9171dcecf8
2 changed files with 545 additions and 0 deletions
20
packages/openapi/lib/documentable.dart
Normal file
20
packages/openapi/lib/documentable.dart
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// This library exports the 'documentable' module from the Protevus OpenAPI package.
|
||||
///
|
||||
/// It provides access to the documentable-related functionality defined in the
|
||||
/// 'src/documentable.dart' file of the 'protevus_openapi' package.
|
||||
///
|
||||
/// This library is useful for working with documentable objects within the
|
||||
/// Protevus platform, allowing developers to utilize the pre-defined
|
||||
/// documentable-related features and structures.
|
||||
library documentable;
|
||||
|
||||
export 'package:protevus_openapi/src/documentable.dart';
|
525
packages/openapi/lib/src/documentable.dart
Normal file
525
packages/openapi/lib/src/documentable.dart
Normal file
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:protevus_openapi/v3.dart';
|
||||
import 'package:protevus_openapi/object.dart';
|
||||
|
||||
/// Defines methods for documenting OpenAPI components.
|
||||
///
|
||||
/// The documentation process calls methods from objects of this type. You implement methods from
|
||||
/// this interface to add reusable components to your OpenAPI document. You may use these components
|
||||
/// when documenting other components or when implementing [APIOperationDocumenter].
|
||||
///
|
||||
/// You must implement [documentComponents].
|
||||
///
|
||||
/// ApplicationChannel, Controller, ManagedEntity, and AuthServer all implement this interface.
|
||||
///
|
||||
abstract class APIComponentDocumenter {
|
||||
/// Instructs this object to add its components to the provided [context].
|
||||
///
|
||||
/// You may register components with [context] in this method. The order in which components
|
||||
/// are registered does not matter.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// class Car implements APIComponentDocumenter {
|
||||
/// @override
|
||||
/// void documentComponents(APIDocumentContext context) {
|
||||
/// context.schema.register("Car", APISchemaObject.object({
|
||||
/// "make": APISchemaObject.string(),
|
||||
/// "model": APISchemaObject.string(),
|
||||
/// "year": APISchemaObject.integer(),
|
||||
/// }));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// See [APIDocumentContext] for more details.
|
||||
void documentComponents(APIDocumentContext context);
|
||||
}
|
||||
|
||||
/// Defines methods for documenting OpenAPI operations in a Controller.
|
||||
///
|
||||
/// The documentation process calls these methods for every Controller in your ApplicationChannel.
|
||||
/// You implement [documentOperations] to create or modify [APIOperation] objects that describe the
|
||||
/// HTTP operations that a controller handler.
|
||||
abstract class APIOperationDocumenter {
|
||||
/// Returns a map of API paths handled by this object.
|
||||
///
|
||||
/// This method is implemented by Router to provide the paths of an OpenAPI document
|
||||
/// and typically shouldn't be overridden by another controller.
|
||||
Map<String, APIPath> documentPaths(APIDocumentContext context);
|
||||
|
||||
/// Documents the API operations handled by this object.
|
||||
///
|
||||
/// You implement this method to create or modify [APIOperation] objects that describe the
|
||||
/// HTTP operations that a controller handles. Each controller in the channel, starting with
|
||||
/// the entry point, have this method.
|
||||
///
|
||||
/// By default, a controller returns the operations created by its linked controllers.
|
||||
///
|
||||
/// Endpoint controllers should override this method to create a [Map] of [APIOperation] objects, where the
|
||||
/// key is a [String] representation of the status code the response is for. Example:
|
||||
///
|
||||
/// @override
|
||||
/// Map<String, APIOperation> documentOperations(APIDocumentContext context, APIPath path) {
|
||||
/// if (path.containsPathParameters(['id'])) {
|
||||
/// return {
|
||||
/// "get": APIOperation("Get one thing", {
|
||||
/// "200": APIResponse(...)
|
||||
/// })
|
||||
/// };
|
||||
/// }
|
||||
///
|
||||
/// return {
|
||||
/// "get": APIOperation("Get some things", {
|
||||
/// "200": APIResponse(...)
|
||||
/// })
|
||||
/// };
|
||||
/// }
|
||||
///
|
||||
/// Middleware controllers should override this method to call the superclass' implementation (which gathers
|
||||
/// the operation objects from an endpoint controller) and then modify those operations before returning them.
|
||||
///
|
||||
/// @override
|
||||
/// Map<String, APIOperation> documentOperations(APIDocumentContext context, APIPath path) {
|
||||
/// final ops = super.documentOperation(context, path);
|
||||
///
|
||||
/// // add x-api-key header parameter to each operation
|
||||
/// ops.values.forEach((op) {
|
||||
/// op.addParameter(new APIParameter.header("x-api-key, schema: new APISchemaObject.string()));
|
||||
/// });
|
||||
///
|
||||
/// return ops;
|
||||
/// }
|
||||
Map<String, APIOperation> documentOperations(
|
||||
APIDocumentContext context,
|
||||
String route,
|
||||
APIPath path,
|
||||
);
|
||||
}
|
||||
|
||||
/// An object that contains information about [APIDocument] being generated.
|
||||
///
|
||||
/// This class serves as a context for the API documentation process, providing access to various
|
||||
/// component collections and utility methods for managing the documentation generation.
|
||||
///
|
||||
/// Component registries for each type of component - e.g. [schema], [responses] - are used to
|
||||
/// register and reference those types.
|
||||
class APIDocumentContext {
|
||||
/// Creates a new [APIDocumentContext] instance.
|
||||
///
|
||||
/// This constructor initializes the context with the provided [document] and sets up
|
||||
/// various [APIComponentCollection] instances for different types of API components.
|
||||
/// These collections are used to manage and reference reusable components throughout
|
||||
/// the API documentation process.
|
||||
///
|
||||
/// The following component collections are initialized:
|
||||
/// - [schema]: For reusable [APISchemaObject] components.
|
||||
/// - [responses]: For reusable [APIResponse] components.
|
||||
/// - [parameters]: For reusable [APIParameter] components.
|
||||
/// - [requestBodies]: For reusable [APIRequestBody] components.
|
||||
/// - [headers]: For reusable [APIHeader] components.
|
||||
/// - [securitySchemes]: For reusable [APISecurityScheme] components.
|
||||
/// - [callbacks]: For reusable [APICallback] components.
|
||||
///
|
||||
/// Each collection is associated with its corresponding component map in the [document].
|
||||
APIDocumentContext(this.document)
|
||||
: schema = APIComponentCollection<APISchemaObject>._(
|
||||
"schemas",
|
||||
document.components!.schemas,
|
||||
),
|
||||
responses = APIComponentCollection<APIResponse>._(
|
||||
"responses",
|
||||
document.components!.responses,
|
||||
),
|
||||
parameters = APIComponentCollection<APIParameter>._(
|
||||
"parameters",
|
||||
document.components!.parameters,
|
||||
),
|
||||
requestBodies = APIComponentCollection<APIRequestBody>._(
|
||||
"requestBodies",
|
||||
document.components!.requestBodies,
|
||||
),
|
||||
headers = APIComponentCollection<APIHeader>._(
|
||||
"headers",
|
||||
document.components!.headers,
|
||||
),
|
||||
securitySchemes = APIComponentCollection<APISecurityScheme>._(
|
||||
"securitySchemes",
|
||||
document.components!.securitySchemes,
|
||||
),
|
||||
callbacks = APIComponentCollection<APICallback>._(
|
||||
"callbacks",
|
||||
document.components!.callbacks,
|
||||
);
|
||||
|
||||
/// The OpenAPI document being created and populated during the documentation process.
|
||||
///
|
||||
/// This [APIDocument] instance represents the root of the OpenAPI specification
|
||||
/// structure. It contains all the components, paths, and other information
|
||||
/// that will be included in the final OpenAPI document.
|
||||
final APIDocument document;
|
||||
|
||||
/// Reusable [APISchemaObject] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable schema components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Schema components are used to define the structure of request and response
|
||||
/// bodies, as well as other data structures used in the API.
|
||||
final APIComponentCollection<APISchemaObject> schema;
|
||||
|
||||
/// Reusable [APIResponse] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable response components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Response components are used to define standard responses that can be
|
||||
/// reused across multiple operations in the API, promoting consistency
|
||||
/// and reducing duplication in the API specification.
|
||||
final APIComponentCollection<APIResponse> responses;
|
||||
|
||||
/// Reusable [APIParameter] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable parameter components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Parameter components are used to define common parameters that can be
|
||||
/// reused across multiple operations in the API, such as query parameters,
|
||||
/// path parameters, or header parameters. This promotes consistency and
|
||||
/// reduces duplication in the API specification.
|
||||
final APIComponentCollection<APIParameter> parameters;
|
||||
|
||||
/// Reusable [APIRequestBody] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable request body components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Request body components are used to define standard request bodies that can be
|
||||
/// reused across multiple operations in the API, promoting consistency
|
||||
/// and reducing duplication in the API specification.
|
||||
final APIComponentCollection<APIRequestBody> requestBodies;
|
||||
|
||||
/// Reusable [APIHeader] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable header components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Header components are used to define common headers that can be
|
||||
/// reused across multiple operations in the API. This promotes consistency
|
||||
/// and reduces duplication in the API specification. Headers can be used
|
||||
/// for various purposes, such as authentication tokens, API versioning,
|
||||
/// or custom metadata.
|
||||
final APIComponentCollection<APIHeader> headers;
|
||||
|
||||
/// Reusable [APISecurityScheme] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable security scheme components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Security scheme components are used to define the security mechanisms that can be
|
||||
/// used across the API. This includes authentication methods such as API keys,
|
||||
/// HTTP authentication, OAuth2 flows, and OpenID Connect. By defining these
|
||||
/// security schemes as reusable components, they can be easily applied to
|
||||
/// different operations or the entire API, ensuring consistent security
|
||||
/// documentation and implementation.
|
||||
final APIComponentCollection<APISecurityScheme> securitySchemes;
|
||||
|
||||
/// Reusable [APICallback] components.
|
||||
///
|
||||
/// This collection manages and provides access to reusable callback components
|
||||
/// in the OpenAPI document. These components can be registered, referenced,
|
||||
/// and retrieved throughout the API documentation process.
|
||||
///
|
||||
/// Callback components are used to define asynchronous, out-of-band requests
|
||||
/// that may be initiated by the API provider after the initial request has been
|
||||
/// processed. They are typically used for webhooks or other event-driven
|
||||
/// interactions. By defining callbacks as reusable components, they can be
|
||||
/// easily referenced and applied to different operations in the API specification,
|
||||
/// promoting consistency and reducing duplication.
|
||||
final APIComponentCollection<APICallback> callbacks;
|
||||
|
||||
/// A list of deferred operations to be executed during the finalization process.
|
||||
///
|
||||
/// This list stores functions that represent asynchronous operations that need to be
|
||||
/// performed before the API documentation is finalized. These operations are typically
|
||||
/// added using the [defer] method and are executed in order during the [finalize] process.
|
||||
List<Function> _deferredOperations = [];
|
||||
|
||||
/// Schedules an asynchronous operation to be executed during the documentation process.
|
||||
///
|
||||
/// Documentation methods are synchronous. Asynchronous methods may be called and awaited on
|
||||
/// in [document]. All [document] closures will be executes and awaited on before finishing [document].
|
||||
/// These closures are called in the order they were added.
|
||||
void defer(FutureOr Function() document) {
|
||||
_deferredOperations.add(document);
|
||||
}
|
||||
|
||||
/// Finalizes the API document and returns it as a serializable [Map].
|
||||
///
|
||||
/// This method is invoked by the command line tool for creating OpenAPI documents.
|
||||
Future<Map<String, dynamic>> finalize() async {
|
||||
final dops = _deferredOperations;
|
||||
_deferredOperations = [];
|
||||
|
||||
await Future.forEach(dops, (Function dop) => dop());
|
||||
|
||||
document.paths!.values
|
||||
.expand((p) => p!.operations.values)
|
||||
.where((op) => op!.security != null)
|
||||
.expand((op) => op!.security!)
|
||||
.forEach((req) {
|
||||
req.requirements!.forEach((schemeName, scopes) {
|
||||
final scheme = document.components!.securitySchemes[schemeName];
|
||||
if (scheme!.type == APISecuritySchemeType.oauth2) {
|
||||
for (final flow in scheme.flows!.values) {
|
||||
for (final scope in scopes) {
|
||||
if (!flow!.scopes!.containsKey(scope)) {
|
||||
flow.scopes![scope] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return document.asMap();
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of reusable OpenAPI objects.
|
||||
///
|
||||
/// This class manages a collection of reusable OpenAPI components of type [T],
|
||||
/// which must extend [APIObject]. It provides methods for registering, retrieving,
|
||||
/// and referencing components within an OpenAPI document.
|
||||
///
|
||||
/// The collection supports two ways of referencing components:
|
||||
/// 1. By name: Components can be registered with a string name and retrieved using that name.
|
||||
/// 2. By type: Components can be associated with a Dart Type and retrieved using that Type.
|
||||
///
|
||||
/// This class is typically used within an [APIDocumentContext] to manage different
|
||||
/// types of OpenAPI components such as schemas, responses, parameters, etc.
|
||||
///
|
||||
/// Key features:
|
||||
/// - Register components with [register]
|
||||
/// - Retrieve components by name with [getObject] or the [] operator
|
||||
/// - Retrieve components by Type with [getObjectWithType]
|
||||
/// - Check if a Type has been registered with [hasRegisteredType]
|
||||
///
|
||||
/// The class also handles deferred resolution of Type-based references, allowing
|
||||
/// components to be referenced before they are fully defined.
|
||||
class APIComponentCollection<T extends APIObject> {
|
||||
/// Creates a new [APIComponentCollection] instance.
|
||||
///
|
||||
/// This constructor is private and is used internally to initialize
|
||||
/// the component collection with a specific type name and component map.
|
||||
///
|
||||
/// [_typeName] is a string that represents the type of components in this collection.
|
||||
/// It is used to construct the reference URIs for the components.
|
||||
///
|
||||
/// [_componentMap] is a map that stores the actual components, with their names as keys.
|
||||
/// This map is used to register and retrieve components by name.
|
||||
APIComponentCollection._(this._typeName, this._componentMap);
|
||||
|
||||
/// The name of the component type managed by this collection.
|
||||
///
|
||||
/// This string is used to construct reference URIs for components in the OpenAPI document.
|
||||
/// It typically corresponds to the plural form of the component type, such as "schemas",
|
||||
/// "responses", "parameters", etc.
|
||||
final String _typeName;
|
||||
|
||||
/// A map that stores the components of type [T] with their names as keys.
|
||||
///
|
||||
/// This map is used to store and retrieve components that have been registered
|
||||
/// with the [APIComponentCollection]. The keys are the names given to the
|
||||
/// components when they are registered, and the values are the actual component
|
||||
/// objects of type [T].
|
||||
///
|
||||
/// This map is populated by the [register] method and accessed by various
|
||||
/// other methods in the class to retrieve registered components.
|
||||
final Map<String, T> _componentMap;
|
||||
|
||||
/// A map that associates Dart types with their corresponding API components.
|
||||
///
|
||||
/// This map is used to store references between Dart types and their registered
|
||||
/// API components. When a component is registered with a specific type using
|
||||
/// the [register] method, an entry is added to this map.
|
||||
///
|
||||
/// The keys are Dart [Type] objects representing the types associated with
|
||||
/// the components, and the values are the corresponding API components of type [T].
|
||||
///
|
||||
/// This map is used internally to resolve type-based references and to check
|
||||
/// if a specific type has been registered using [hasRegisteredType].
|
||||
final Map<Type, T> _typeReferenceMap = {};
|
||||
|
||||
/// A map that stores [Completer] objects for deferred type resolution.
|
||||
///
|
||||
/// This map is used to handle cases where a component is referenced by its Type
|
||||
/// before it has been registered. The keys are Dart [Type] objects, and the values
|
||||
/// are [Completer] objects that will be completed when the corresponding component
|
||||
/// is registered.
|
||||
///
|
||||
/// When a component is requested by type using [getObjectWithType] and it hasn't
|
||||
/// been registered yet, a new [Completer] is added to this map. Later, when the
|
||||
/// component is registered using [register], the corresponding [Completer] is
|
||||
/// completed, allowing any pending references to be resolved.
|
||||
///
|
||||
/// This mechanism enables forward references in the API documentation process,
|
||||
/// allowing components to be used before they are fully defined.
|
||||
final Map<Type, Completer<T>> _resolutionMap = {};
|
||||
|
||||
/// Registers a component with a given name and optionally associates it with a Type.
|
||||
///
|
||||
/// [component] will be stored in the OpenAPI document. The component will be usable
|
||||
/// by other objects by its [name].
|
||||
///
|
||||
/// If this component is represented by a class, provide it as [representation].
|
||||
/// Objects may reference either [name] or [representation] when using a component.
|
||||
void register(String name, T component, {Type? representation}) {
|
||||
if (_componentMap.containsKey(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (representation != null &&
|
||||
_typeReferenceMap.containsKey(representation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_componentMap[name] = component;
|
||||
|
||||
if (representation != null) {
|
||||
final refObject = getObject(name);
|
||||
_typeReferenceMap[representation] = refObject;
|
||||
|
||||
if (_resolutionMap.containsKey(representation)) {
|
||||
_resolutionMap[representation]!.complete(refObject);
|
||||
_resolutionMap.remove(representation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference object in this collection with the given [name].
|
||||
///
|
||||
/// See [getObject].
|
||||
T operator [](String name) => getObject(name);
|
||||
|
||||
/// Returns an object that references a component named [name].
|
||||
///
|
||||
/// This method creates and returns a reference object of type [T] that points to
|
||||
/// a component in the OpenAPI document with the given [name]. The returned object
|
||||
/// is always a reference; it does not contain the actual values of the component.
|
||||
///
|
||||
/// An object is always returned, even if no component named [name] exists.
|
||||
/// If after [APIDocumentContext.finalize] is called and no object
|
||||
/// has been registered for [name], an error is thrown.
|
||||
T getObject(String name) {
|
||||
final obj = _getInstanceOf();
|
||||
obj.referenceURI = Uri(path: "/components/$_typeName/$name");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// Returns an object that references a component registered for [type].
|
||||
///
|
||||
/// This method creates and returns a reference object of type [T] that points to
|
||||
/// a component in the OpenAPI document associated with the given [type].
|
||||
///
|
||||
/// An object is always returned, even if no component named has been registered
|
||||
/// for [type]. If after [APIDocumentContext.finalize] is called and no object
|
||||
/// has been registered for [type], an error is thrown.
|
||||
T getObjectWithType(Type type) {
|
||||
final obj = _getInstanceOf();
|
||||
obj.referenceURI =
|
||||
Uri(path: "/components/$_typeName/conduit-typeref:$type");
|
||||
|
||||
if (_typeReferenceMap.containsKey(type)) {
|
||||
obj.referenceURI = _typeReferenceMap[type]!.referenceURI;
|
||||
} else {
|
||||
final completer =
|
||||
_resolutionMap.putIfAbsent(type, () => Completer<T>.sync());
|
||||
|
||||
completer.future.then((refObject) {
|
||||
obj.referenceURI = refObject.referenceURI;
|
||||
});
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// Creates and returns an empty instance of type [T].
|
||||
///
|
||||
/// This method is used internally to create empty instances of various API components
|
||||
/// based on the generic type [T]. It supports the following types:
|
||||
/// - [APISchemaObject]
|
||||
/// - [APIResponse]
|
||||
/// - [APIParameter]
|
||||
/// - [APIRequestBody]
|
||||
/// - [APIHeader]
|
||||
/// - [APISecurityScheme]
|
||||
/// - [APICallback]
|
||||
///
|
||||
/// For each supported type, it calls the corresponding `empty()` constructor
|
||||
/// and casts the result to type [T].
|
||||
///
|
||||
/// If [T] is not one of the supported types, this method throws a [StateError]
|
||||
/// with a message indicating that it cannot reference an API object of that type.
|
||||
///
|
||||
/// Returns: An empty instance of type [T].
|
||||
///
|
||||
/// Throws: [StateError] if [T] is not a supported API object type.
|
||||
T _getInstanceOf() {
|
||||
switch (T) {
|
||||
case const (APISchemaObject):
|
||||
return APISchemaObject.empty() as T;
|
||||
case const (APIResponse):
|
||||
return APIResponse.empty() as T;
|
||||
case const (APIParameter):
|
||||
return APIParameter.empty() as T;
|
||||
case const (APIRequestBody):
|
||||
return APIRequestBody.empty() as T;
|
||||
case const (APIHeader):
|
||||
return APIHeader.empty() as T;
|
||||
case const (APISecurityScheme):
|
||||
return APISecurityScheme.empty() as T;
|
||||
case const (APICallback):
|
||||
return APICallback.empty() as T;
|
||||
}
|
||||
|
||||
throw StateError("cannot reference API object of type $T");
|
||||
}
|
||||
|
||||
/// Checks if a specific Type has been registered with this component collection.
|
||||
///
|
||||
/// This method returns true if a component has been registered for the given [type]
|
||||
/// using the [register] method with a non-null [representation] parameter.
|
||||
///
|
||||
/// Parameters:
|
||||
/// [type] - The Type to check for registration.
|
||||
///
|
||||
/// Returns:
|
||||
/// A boolean value indicating whether the [type] has been registered (true) or not (false).
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final collection = APIComponentCollection<APISchemaObject>(...);
|
||||
/// collection.register('User', userSchema, representation: User);
|
||||
///
|
||||
/// assert(collection.hasRegisteredType(User) == true);
|
||||
/// assert(collection.hasRegisteredType(String) == false);
|
||||
/// ```
|
||||
bool hasRegisteredType(Type type) {
|
||||
return _typeReferenceMap.containsKey(type);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue