Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ public void AddDefinitions(
OpenApiEndpointDefinition[] endpoints,
OpenApiModelDefinition[] models,
IDictionary<string, OpenApiModelDefinition> modelsByName,
ISchemaDefinition schema)
ISchemaDefinition schema,
IOpenApiDiagnosticEvents events)
{
var (endpointDescriptors, modelDescriptors, inputComponents) =
new DocumentBuilder(schema, modelsByName).Build(endpoints, models);
new DocumentBuilder(schema, modelsByName, events).Build(endpoints, models);

_endpoints = endpointDescriptors;
_models = modelDescriptors;
Expand Down Expand Up @@ -180,10 +181,12 @@ public void Add(OpenApiEndpointDefinitionParameter parameter)
// ambient values (schema, models) are read from fields instead of threaded as parameters.
private sealed class DocumentBuilder(
ISchemaDefinition schema,
IDictionary<string, OpenApiModelDefinition> modelsByName)
IDictionary<string, OpenApiModelDefinition> modelsByName,
IOpenApiDiagnosticEvents events)
{
private readonly ISchemaDefinition _schema = schema;
private readonly IDictionary<string, OpenApiModelDefinition> _modelsByName = modelsByName;
private readonly IOpenApiDiagnosticEvents _events = events;
private readonly Dictionary<string, int> _operationIdUsages = [];
private readonly Dictionary<string, OpenApiSchemaAbstraction> _inputComponents = [];
private readonly HashSet<string> _registeredInputNames = [];
Expand All @@ -201,9 +204,16 @@ private sealed class DocumentBuilder(
var endpointDescriptor = CreateEndpointDescriptor(endpoint);
endpointDescriptors.Add(endpointDescriptor);
}
catch
catch (Exception exception)
{
// If the construction of an endpoint descriptor fails, we just skip over it.
// If the construction of an endpoint descriptor fails, we skip over it and
// report why the endpoint is missing from the OpenAPI document.
var endpointName = endpoint.OperationDefinition.Name?.Value;

ReportError(
$"Endpoint '{endpointName}' could not be added to the OpenAPI document: "
+ exception.Message,
endpoint);
}
}

Expand All @@ -214,9 +224,14 @@ private sealed class DocumentBuilder(
var modelDescriptor = CreateModelDescriptor(model);
modelDescriptors.Add(modelDescriptor);
}
catch
catch (Exception exception)
{
// If the construction of an model descriptor fails, we just skip over it.
// If the construction of an model descriptor fails, we skip over it and
// report why the model is missing from the OpenAPI document.
ReportError(
$"Model '{model.Name}' could not be added to the OpenAPI document: "
+ exception.Message,
model);
}
}

Expand Down Expand Up @@ -537,9 +552,22 @@ private OpenApiSchemaAbstraction CreateOpenApiSchemaForSelectionSet(
var fieldName = field.Name.Value;
var responseName = field.Alias?.Value ?? fieldName;

var fieldType = fieldName == IntrospectionFieldNames.TypeName ?
new NonNullType(_schema.Types["String"]) :
((IComplexTypeDefinition)namedType).Fields[fieldName].Type;
IOutputType fieldType;

if (fieldName == IntrospectionFieldNames.TypeName)
{
fieldType = new NonNullType(_schema.Types["String"]);
}
else if (namedType is IComplexTypeDefinition complexType
&& complexType.Fields.TryGetField(fieldName, out var fieldDefinition))
{
fieldType = fieldDefinition.Type;
}
else
{
throw new InvalidOperationException(
$"Expected to find field '{fieldName}' on type '{namedType.Name}'.");
}

OpenApiSchemaAbstraction typeSchema;
if (field.SelectionSet is not null)
Expand Down Expand Up @@ -622,7 +650,12 @@ private OpenApiSchemaAbstraction CreateOpenApiSchemaForSelectionSet(
}
else
{
var fragment = localFragmentLookup[fragmentName];
if (!localFragmentLookup.TryGetValue(fragmentName, out var fragment))
{
throw new InvalidOperationException(
$"Expected to find a definition for fragment '{fragmentName}'.");
}

var typeCondition = _schema.Types[fragment.TypeCondition.Name.Value];
var isDifferentType = !typeCondition.IsAssignableFrom(namedType);
var typeName = typeCondition.Name;
Expand Down Expand Up @@ -1301,5 +1334,10 @@ private static string GetOperationId(string? operationName)
span[0] = char.ToLowerInvariant(span[0]);
});
}

private void ReportError(string message, IOpenApiDefinition definition)
{
_events.ValidationErrors([new OpenApiDefinitionValidationError(message, definition)]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ namespace HotChocolate.Adapters.OpenApi;

internal sealed record OpenApiEndpointDescriptor(
DocumentNode Document,
bool HasValidDocument,
IReadOnlyList<IError> DocumentErrors,
string HttpMethod,
RoutePattern Route,
VariableValueInsertionTrie ParameterTrie,
string? VariableFilledThroughBody,
OpenApiResponseBodySelection ResponseBodySelection);
OpenApiResponseBodySelection ResponseBodySelection)
{
public bool HasValidDocument => DocumentErrors.Count == 0;
}

internal interface IVariableValueInsertionTrieSegment;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,10 @@ public static OpenApiEndpointDescriptor CreateEndpointDescriptor(
var documentValidator = schema.Services.GetRequiredService<DocumentValidator>();

var validationResult = documentValidator.Validate(schema, document);
var hasValidDocument = !validationResult.HasErrors;

return new OpenApiEndpointDescriptor(
document,
hasValidDocument,
validationResult.Errors,
endpointDefinition.HttpMethod,
route,
parameterTrie,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ void AddDefinitions(
OpenApiEndpointDefinition[] endpoints,
OpenApiModelDefinition[] models,
IDictionary<string, OpenApiModelDefinition> modelsByName,
ISchemaDefinition schema);
ISchemaDefinition schema,
IOpenApiDiagnosticEvents events);
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,13 @@ private void UpdateAllDefinitions(
validDefinitions.Add(definition);
}

UpdateEndpointsAndOpenApiDefinitions(validDefinitions, schema);
UpdateEndpointsAndOpenApiDefinitions(validDefinitions, schema, events);
}

private void UpdateEndpointsAndOpenApiDefinitions(
List<IOpenApiDefinition> definitions,
ISchemaDefinition schema)
ISchemaDefinition schema,
IOpenApiDiagnosticEvents events)
{
var endpoints = definitions
.OfType<OpenApiEndpointDefinition>()
Expand Down Expand Up @@ -191,14 +192,23 @@ private void UpdateEndpointsAndOpenApiDefinitions(
continue;
}

var endpointName = endpoint.OperationDefinition.Name!.Value;

OpenApiEndpointDescriptor descriptor;

try
{
descriptor = OpenApiEndpointFactory.CreateEndpointDescriptor(endpoint, modelsByName, schema);
}
catch
catch (Exception exception)
{
events.ValidationErrors(
[
new OpenApiDefinitionValidationError(
$"Endpoint '{endpointName}' could not be initialized: {exception.Message}",
endpoint)
]);

continue;
}

Expand All @@ -217,18 +227,35 @@ private void UpdateEndpointsAndOpenApiDefinitions(

keyHasValid.Add(key);
}
else if (!keyToIndex.ContainsKey(key))
else
{
keyToIndex[key] = chosenEndpoints.Count;
chosenEndpoints.Add((endpoint, descriptor));
events.ValidationErrors(
[
.. descriptor.DocumentErrors.Select(
error => new OpenApiDefinitionValidationError(
$"Endpoint '{endpointName}' has an invalid document: {error.Message}",
endpoint))
]);

if (!keyToIndex.ContainsKey(key))
{
keyToIndex[key] = chosenEndpoints.Count;
chosenEndpoints.Add((endpoint, descriptor));
}
}
}

// Endpoints with an invalid document cannot execute, so they are left out of the
// OpenAPI document rather than described as callable.
_transformer.AddDefinitions(
chosenEndpoints.Select(e => e.Definition).ToArray(),
chosenEndpoints
.Where(e => e.Descriptor.HasValidDocument)
.Select(e => e.Definition)
.ToArray(),
models,
modelsByName,
schema);
schema,
events);

var httpEndpoints = new List<Endpoint>();

Expand Down
Loading
Loading