void Generator::outputObjectImplementation()

in src/SchemaGenerator.cpp [1850:2946]


void Generator::outputObjectImplementation(
	std::ostream& sourceFile, const ObjectType& objectType, bool isQueryType) const
{
	using namespace std::literals;

	if (_loader.isIntrospection())
	{
		// Output the public constructor which calls through to the service::Object constructor
		// with arguments that declare the set of types it implements and bind the fields to the
		// resolver methods.
		sourceFile << objectType.cppType << R"cpp(::)cpp" << objectType.cppType
				   << R"cpp((std::shared_ptr<)cpp" << SchemaLoader::getIntrospectionNamespace()
				   << R"cpp(::)cpp" << objectType.cppType << R"cpp(> pimpl))cpp";
	}
	else
	{
		// Output the private constructor which calls through to the service::Object constructor
		// with arguments that declare the set of types it implements and bind the fields to the
		// resolver methods.
		sourceFile << objectType.cppType << R"cpp(::)cpp" << objectType.cppType
				   << R"cpp((std::unique_ptr<const Concept>&& pimpl))cpp";
	}

	sourceFile << R"cpp( noexcept
	: service::Object{ getTypeNames(), getResolvers() })cpp";

	if (!_options.noIntrospection && isQueryType)
	{
		sourceFile << R"cpp(
	, _schema { GetSchema() })cpp";
	}

	if (_loader.isIntrospection())
	{
		sourceFile << R"cpp(
	, _pimpl { std::make_unique<Model<)cpp"
				   << SchemaLoader::getIntrospectionNamespace() << R"cpp(::)cpp"
				   << objectType.cppType << R"cpp(>>(std::move(pimpl)) })cpp";
	}
	else
	{
		sourceFile << R"cpp(
	, _pimpl { std::move(pimpl) })cpp";
	}
	sourceFile << R"cpp(
{
}
)cpp";

	if (_loader.isIntrospection())
	{
		sourceFile << R"cpp(
)cpp" << objectType.cppType
				   << R"cpp(::~)cpp" << objectType.cppType << R"cpp(()
{
	// This is empty, but explicitly defined here so that it can access the un-exported destructor
	// of the implementation type.
}
)cpp";
	}

	sourceFile << R"cpp(
service::TypeNames )cpp"
			   << objectType.cppType << R"cpp(::getTypeNames() const noexcept
{
	return {
)cpp";

	for (const auto& interfaceName : objectType.interfaces)
	{
		sourceFile << R"cpp(		R"gql()cpp" << interfaceName << R"cpp()gql"sv,
)cpp";
	}

	for (const auto& unionName : objectType.unions)
	{
		sourceFile << R"cpp(		R"gql()cpp" << unionName << R"cpp()gql"sv,
)cpp";
	}

	sourceFile << R"cpp(		R"gql()cpp" << objectType.type << R"cpp()gql"sv
	};
}

service::ResolverMap )cpp"
			   << objectType.cppType << R"cpp(::getResolvers() const noexcept
{
	return {
)cpp";

	std::map<std::string_view, std::string, internal::shorter_or_less> resolvers;

	std::transform(objectType.fields.cbegin(),
		objectType.fields.cend(),
		std::inserter(resolvers, resolvers.begin()),
		[](const OutputField& outputField) noexcept {
			std::string fieldName(outputField.cppName);

			fieldName[0] =
				static_cast<char>(std::toupper(static_cast<unsigned char>(fieldName[0])));

			std::ostringstream output;

			output << R"cpp(		{ R"gql()cpp" << outputField.name
				   << R"cpp()gql"sv, [this](service::ResolverParams&& params) { return resolve)cpp"
				   << fieldName << R"cpp((std::move(params)); } })cpp";

			return std::make_pair(std::string_view { outputField.name }, output.str());
		});

	resolvers["__typename"sv] =
		R"cpp(		{ R"gql(__typename)gql"sv, [this](service::ResolverParams&& params) { return resolve_typename(std::move(params)); } })cpp"s;

	if (!_options.noIntrospection && isQueryType)
	{
		resolvers["__schema"sv] =
			R"cpp(		{ R"gql(__schema)gql"sv, [this](service::ResolverParams&& params) { return resolve_schema(std::move(params)); } })cpp"s;
		resolvers["__type"sv] =
			R"cpp(		{ R"gql(__type)gql"sv, [this](service::ResolverParams&& params) { return resolve_type(std::move(params)); } })cpp"s;
	}

	bool firstField = true;

	for (const auto& resolver : resolvers)
	{
		if (!firstField)
		{
			sourceFile << R"cpp(,
)cpp";
		}

		firstField = false;
		sourceFile << resolver.second;
	}

	sourceFile << R"cpp(
	};
}
)cpp";

	if (!_loader.isIntrospection())
	{
		sourceFile << R"cpp(
void )cpp" << objectType.cppType
				   << R"cpp(::beginSelectionSet(const service::SelectionSetParams& params) const
{
	_pimpl->beginSelectionSet(params);
}

void )cpp" << objectType.cppType
				   << R"cpp(::endSelectionSet(const service::SelectionSetParams& params) const
{
	_pimpl->endSelectionSet(params);
}
)cpp";
	}

	// Output each of the resolver implementations, which call the virtual property
	// getters that the implementer must define.
	for (const auto& outputField : objectType.fields)
	{
		std::string fieldName(outputField.cppName);

		fieldName[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(fieldName[0])));

		sourceFile << R"cpp(
service::AwaitableResolver )cpp"
				   << objectType.cppType << R"cpp(::resolve)cpp" << fieldName
				   << R"cpp((service::ResolverParams&& params) const
{
)cpp";

		// Output a preamble to retrieve all of the arguments from the resolver parameters.
		if (!outputField.arguments.empty())
		{
			bool firstArgument = true;

			for (const auto& argument : outputField.arguments)
			{
				if (argument.defaultValue.type() != response::Type::Null)
				{
					if (firstArgument)
					{
						firstArgument = false;
						sourceFile << R"cpp(	static const auto defaultArguments = []()
	{
		response::Value values(response::Type::Map);
		response::Value entry;

)cpp";
					}

					sourceFile << getArgumentDefaultValue(0, argument.defaultValue)
							   << R"cpp(		values.emplace_back(")cpp" << argument.name
							   << R"cpp(", std::move(entry));
)cpp";
				}
			}

			if (!firstArgument)
			{
				sourceFile << R"cpp(
		return values;
	}();

)cpp";
			}

			for (const auto& argument : outputField.arguments)
			{
				sourceFile << getArgumentDeclaration(argument,
					"arg",
					"params.arguments",
					"defaultArguments");
			}
		}

		sourceFile << R"cpp(	std::unique_lock resolverLock(_resolverMutex);
)cpp";

		if (!_loader.isIntrospection())
		{
			sourceFile << R"cpp(	auto directives = std::move(params.fieldDirectives);
)cpp";
		}

		sourceFile << R"cpp(	auto result = _pimpl->)cpp" << outputField.accessor << fieldName
				   << R"cpp(()cpp";

		bool firstArgument = _loader.isIntrospection();

		if (!firstArgument)
		{
			sourceFile
				<< R"cpp(service::FieldParams(service::SelectionSetParams{ params }, std::move(directives)))cpp";
		}

		if (!outputField.arguments.empty())
		{
			for (const auto& argument : outputField.arguments)
			{
				std::string argumentName(argument.cppName);

				argumentName[0] =
					static_cast<char>(std::toupper(static_cast<unsigned char>(argumentName[0])));

				if (!firstArgument)
				{
					sourceFile << R"cpp(, )cpp";
				}

				sourceFile << R"cpp(std::move(arg)cpp" << argumentName << R"cpp())cpp";
				firstArgument = false;
			}
		}

		sourceFile << R"cpp();
	resolverLock.unlock();

	return )cpp" << getResultAccessType(outputField)
				   << R"cpp(::convert)cpp" << getTypeModifiers(outputField.modifiers)
				   << R"cpp((std::move(result), std::move(params));
}
)cpp";
	}

	sourceFile << R"cpp(
service::AwaitableResolver )cpp"
			   << objectType.cppType
			   << R"cpp(::resolve_typename(service::ResolverParams&& params) const
{
	return service::ModifiedResult<std::string>::convert(std::string{ R"gql()cpp"
			   << objectType.type << R"cpp()gql" }, std::move(params));
}
)cpp";

	if (!_options.noIntrospection && isQueryType)
	{
		sourceFile
			<< R"cpp(
service::AwaitableResolver )cpp"
			<< objectType.cppType << R"cpp(::resolve_schema(service::ResolverParams&& params) const
{
	return service::ModifiedResult<service::Object>::convert(std::static_pointer_cast<service::Object>(std::make_shared<)cpp"
			<< SchemaLoader::getIntrospectionNamespace()
			<< R"cpp(::object::Schema>(std::make_shared<)cpp"
			<< SchemaLoader::getIntrospectionNamespace()
			<< R"cpp(::Schema>(_schema))), std::move(params));
}

service::AwaitableResolver )cpp"
			<< objectType.cppType << R"cpp(::resolve_type(service::ResolverParams&& params) const
{
	auto argName = service::ModifiedArgument<std::string>::require("name", params.arguments);
	const auto& baseType = _schema->LookupType(argName);
	std::shared_ptr<)cpp"
			<< SchemaLoader::getIntrospectionNamespace()
			<< R"cpp(::object::Type> result { baseType ? std::make_shared<)cpp"
			<< SchemaLoader::getIntrospectionNamespace()
			<< R"cpp(::object::Type>(std::make_shared<)cpp"
			<< SchemaLoader::getIntrospectionNamespace() << R"cpp(::Type>(baseType)) : nullptr };

	return service::ModifiedResult<)cpp"
			<< SchemaLoader::getIntrospectionNamespace()
			<< R"cpp(::object::Type>::convert<service::TypeModifier::Nullable>(result, std::move(params));
}
)cpp";
	}
}

void Generator::outputObjectIntrospection(
	std::ostream& sourceFile, const ObjectType& objectType) const
{
	outputIntrospectionInterfaces(sourceFile, objectType.cppType, objectType.interfaces);
	outputIntrospectionFields(sourceFile, objectType.cppType, objectType.fields);
}

void Generator::outputIntrospectionInterfaces(std::ostream& sourceFile, std::string_view cppType,
	const std::vector<std::string_view>& interfaces) const
{
	if (!interfaces.empty())
	{
		bool firstInterface = true;

		sourceFile << R"cpp(	type)cpp" << cppType << R"cpp(->AddInterfaces({
)cpp";

		for (const auto& interfaceName : interfaces)
		{
			if (!firstInterface)
			{
				sourceFile << R"cpp(,
)cpp";
			}

			firstInterface = false;

			sourceFile
				<< R"cpp(		std::static_pointer_cast<const schema::InterfaceType>(schema->LookupType(R"gql()cpp"
				<< interfaceName << R"cpp()gql"sv)))cpp";
		}

		sourceFile << R"cpp(
	});
)cpp";
	}
}

void Generator::outputIntrospectionFields(
	std::ostream& sourceFile, std::string_view cppType, const OutputFieldList& fields) const
{
	if (fields.empty())
	{
		return;
	}

	bool firstValue = true;

	sourceFile << R"cpp(	type)cpp" << cppType << R"cpp(->AddFields({
)cpp";

	for (const auto& objectField : fields)
	{
		if (!firstValue)
		{
			sourceFile << R"cpp(,
)cpp";
		}

		firstValue = false;
		sourceFile << R"cpp(		schema::Field::Make(R"gql()cpp" << objectField.name
				   << R"cpp()gql"sv, R"md()cpp";

		if (!_options.noIntrospection)
		{
			sourceFile << objectField.description;
		}

		sourceFile << R"cpp()md"sv, )cpp";

		if (objectField.deprecationReason)
		{
			sourceFile << R"cpp(std::make_optional(R"md()cpp" << *objectField.deprecationReason
					   << R"cpp()md"sv))cpp";
		}
		else
		{
			sourceFile << R"cpp(std::nullopt)cpp";
		}

		sourceFile << R"cpp(, )cpp"
				   << getIntrospectionType(objectField.type, objectField.modifiers);

		if (!objectField.arguments.empty())
		{
			bool firstArgument = true;

			sourceFile << R"cpp(, {
)cpp";

			for (const auto& argument : objectField.arguments)
			{
				if (!firstArgument)
				{
					sourceFile << R"cpp(,
)cpp";
				}

				firstArgument = false;
				sourceFile << R"cpp(			schema::InputValue::Make(R"gql()cpp"
						   << argument.name << R"cpp()gql"sv, R"md()cpp";

				if (!_options.noIntrospection)
				{
					sourceFile << argument.description;
				}

				sourceFile << R"cpp()md"sv, )cpp"
						   << getIntrospectionType(argument.type, argument.modifiers)
						   << R"cpp(, R"gql()cpp" << argument.defaultValueString
						   << R"cpp()gql"sv))cpp";
			}

			sourceFile << R"cpp(
		})cpp";
		}

		sourceFile << R"cpp())cpp";
	}

	sourceFile << R"cpp(
	});
)cpp";
}

std::string Generator::getArgumentDefaultValue(
	size_t level, const response::Value& defaultValue) const noexcept
{
	const std::string padding(level, '\t');
	std::ostringstream argumentDefaultValue;

	switch (defaultValue.type())
	{
		case response::Type::Map:
		{
			const auto& members = defaultValue.get<response::MapType>();

			argumentDefaultValue << padding << R"cpp(		entry = []()
)cpp" << padding << R"cpp(		{
)cpp" << padding << R"cpp(			response::Value members(response::Type::Map);
)cpp" << padding << R"cpp(			response::Value entry;

)cpp";

			for (const auto& entry : members)
			{
				argumentDefaultValue << getArgumentDefaultValue(level + 1, entry.second) << padding
									 << R"cpp(			members.emplace_back(")cpp" << entry.first
									 << R"cpp(", std::move(entry));
)cpp";
			}

			argumentDefaultValue << padding << R"cpp(			return members;
)cpp" << padding << R"cpp(		}();
)cpp";
			break;
		}

		case response::Type::List:
		{
			const auto& elements = defaultValue.get<response::ListType>();

			argumentDefaultValue << padding << R"cpp(		entry = []()
)cpp" << padding << R"cpp(		{
)cpp" << padding << R"cpp(			response::Value elements(response::Type::List);
)cpp" << padding << R"cpp(			response::Value entry;

)cpp";

			for (const auto& entry : elements)
			{
				argumentDefaultValue << getArgumentDefaultValue(level + 1, entry) << padding
									 << R"cpp(			elements.emplace_back(std::move(entry));
)cpp";
			}

			argumentDefaultValue << padding << R"cpp(			return elements;
)cpp" << padding << R"cpp(		}();
)cpp";
			break;
		}

		case response::Type::String:
		{
			argumentDefaultValue << padding
								 << R"cpp(		entry = response::Value(std::string(R"gql()cpp"
								 << defaultValue.get<std::string>() << R"cpp()gql"));
)cpp";
			break;
		}

		case response::Type::Null:
		{
			argumentDefaultValue << padding << R"cpp(		entry = {};
)cpp";
			break;
		}

		case response::Type::Boolean:
		{
			argumentDefaultValue << padding << R"cpp(		entry = response::Value()cpp"
								 << (defaultValue.get<bool>() ? R"cpp(true)cpp" : R"cpp(false)cpp")
								 << R"cpp();
)cpp";
			break;
		}

		case response::Type::Int:
		{
			argumentDefaultValue << padding
								 << R"cpp(		entry = response::Value(static_cast<int>()cpp"
								 << defaultValue.get<int>() << R"cpp());
)cpp";
			break;
		}

		case response::Type::Float:
		{
			argumentDefaultValue << padding
								 << R"cpp(		entry = response::Value(static_cast<double>()cpp"
								 << defaultValue.get<double>() << R"cpp());
)cpp";
			break;
		}

		case response::Type::EnumValue:
		{
			argumentDefaultValue
				<< padding << R"cpp(		entry = response::Value(response::Type::EnumValue);
		entry.set<std::string>(R"gql()cpp"
				<< defaultValue.get<std::string>() << R"cpp()gql");
)cpp";
			break;
		}

		case response::Type::Scalar:
		{
			argumentDefaultValue << padding << R"cpp(		entry = []()
)cpp" << padding << R"cpp(		{
)cpp" << padding << R"cpp(			response::Value scalar(response::Type::Scalar);
)cpp" << padding << R"cpp(			response::Value entry;

)cpp";
			argumentDefaultValue
				<< padding << R"cpp(	)cpp"
				<< getArgumentDefaultValue(level + 1, defaultValue.get<response::ScalarType>())
				<< padding << R"cpp(			scalar.set<response::ScalarType>(std::move(entry));

)cpp" << padding << R"cpp(			return scalar;
)cpp" << padding << R"cpp(		}();
)cpp";
			break;
		}
	}

	return argumentDefaultValue.str();
}

std::string Generator::getArgumentDeclaration(const InputField& argument, const char* prefixToken,
	const char* argumentsToken, const char* defaultToken) const noexcept
{
	std::ostringstream argumentDeclaration;
	std::string argumentName(argument.cppName);

	argumentName[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(argumentName[0])));
	if (argument.defaultValue.type() == response::Type::Null)
	{
		argumentDeclaration << R"cpp(	auto )cpp" << prefixToken << argumentName << R"cpp( = )cpp"
							<< getArgumentAccessType(argument) << R"cpp(::require)cpp"
							<< getTypeModifiers(argument.modifiers) << R"cpp((")cpp"
							<< argument.name << R"cpp(", )cpp" << argumentsToken << R"cpp();
)cpp";
	}
	else
	{
		argumentDeclaration << R"cpp(	auto pair)cpp" << argumentName << R"cpp( = )cpp"
							<< getArgumentAccessType(argument) << R"cpp(::find)cpp"
							<< getTypeModifiers(argument.modifiers) << R"cpp((")cpp"
							<< argument.name << R"cpp(", )cpp" << argumentsToken << R"cpp();
	auto )cpp" << prefixToken
							<< argumentName << R"cpp( = (pair)cpp" << argumentName << R"cpp(.second
		? std::move(pair)cpp"
							<< argumentName << R"cpp(.first)
		: )cpp" << getArgumentAccessType(argument)
							<< R"cpp(::require)cpp" << getTypeModifiers(argument.modifiers)
							<< R"cpp((")cpp" << argument.name << R"cpp(", )cpp" << defaultToken
							<< R"cpp());
)cpp";
	}

	return argumentDeclaration.str();
}

std::string Generator::getArgumentAccessType(const InputField& argument) const noexcept
{
	std::ostringstream argumentType;

	argumentType << R"cpp(service::ModifiedArgument<)cpp";

	switch (argument.fieldType)
	{
		case InputFieldType::Builtin:
			argumentType << _loader.getCppType(argument.type);
			break;

		case InputFieldType::Enum:
		case InputFieldType::Input:
			argumentType << _loader.getSchemaNamespace() << R"cpp(::)cpp"
						 << _loader.getCppType(argument.type);
			break;

		case InputFieldType::Scalar:
			argumentType << R"cpp(response::Value)cpp";
			break;
	}

	argumentType << R"cpp(>)cpp";

	return argumentType.str();
}

std::string Generator::getResultAccessType(const OutputField& result) const noexcept
{
	std::ostringstream resultType;

	resultType << R"cpp(service::ModifiedResult<)cpp";

	switch (result.fieldType)
	{
		case OutputFieldType::Builtin:
		case OutputFieldType::Enum:
		case OutputFieldType::Interface:
		case OutputFieldType::Union:
		case OutputFieldType::Object:
			resultType << _loader.getCppType(result.type);
			break;

		case OutputFieldType::Scalar:
			resultType << R"cpp(response::Value)cpp";
			break;
	}

	resultType << R"cpp(>)cpp";

	return resultType.str();
}

std::string Generator::getTypeModifiers(const TypeModifierStack& modifiers) const noexcept
{
	bool firstValue = true;
	std::ostringstream typeModifiers;

	for (auto modifier : modifiers)
	{
		if (firstValue)
		{
			typeModifiers << R"cpp(<)cpp";
			firstValue = false;
		}
		else
		{
			typeModifiers << R"cpp(, )cpp";
		}

		switch (modifier)
		{
			case service::TypeModifier::Nullable:
				typeModifiers << R"cpp(service::TypeModifier::Nullable)cpp";
				break;

			case service::TypeModifier::List:
				typeModifiers << R"cpp(service::TypeModifier::List)cpp";
				break;

			case service::TypeModifier::None:
				break;
		}
	}

	if (!firstValue)
	{
		typeModifiers << R"cpp(>)cpp";
	}

	return typeModifiers.str();
}

std::string Generator::getIntrospectionType(
	std::string_view type, const TypeModifierStack& modifiers) const noexcept
{
	size_t wrapperCount = 0;
	bool nonNull = true;
	std::ostringstream introspectionType;

	for (auto modifier : modifiers)
	{
		if (nonNull)
		{
			switch (modifier)
			{
				case service::TypeModifier::None:
				case service::TypeModifier::List:
				{
					introspectionType << R"cpp(schema->WrapType()cpp"
									  << SchemaLoader::getIntrospectionNamespace()
									  << R"cpp(::TypeKind::NON_NULL, )cpp";
					++wrapperCount;
					break;
				}

				case service::TypeModifier::Nullable:
				{
					// If the next modifier is Nullable that cancels the non-nullable state.
					nonNull = false;
					break;
				}
			}
		}

		switch (modifier)
		{
			case service::TypeModifier::None:
			{
				nonNull = true;
				break;
			}

			case service::TypeModifier::List:
			{
				nonNull = true;
				introspectionType << R"cpp(schema->WrapType()cpp"
								  << SchemaLoader::getIntrospectionNamespace()
								  << R"cpp(::TypeKind::LIST, )cpp";
				++wrapperCount;
				break;
			}

			case service::TypeModifier::Nullable:
				break;
		}
	}

	if (nonNull)
	{
		introspectionType << R"cpp(schema->WrapType()cpp"
						  << SchemaLoader::getIntrospectionNamespace()
						  << R"cpp(::TypeKind::NON_NULL, )cpp";
		++wrapperCount;
	}

	introspectionType << R"cpp(schema->LookupType(R"gql()cpp" << type << R"cpp()gql"sv))cpp";

	for (size_t i = 0; i < wrapperCount; ++i)
	{
		introspectionType << R"cpp())cpp";
	}

	return introspectionType.str();
}

std::vector<std::string> Generator::outputSeparateFiles() const noexcept
{
	const std::filesystem::path headerDir(_headerDir);
	const std::filesystem::path sourceDir(_sourceDir);
	std::vector<std::string> files;
	std::string_view queryType;

	for (const auto& operation : _loader.getOperationTypes())
	{
		if (operation.operation == service::strQuery)
		{
			queryType = operation.type;
			break;
		}
	}

	std::ostringstream ossNamespace;

	ossNamespace << R"cpp(graphql::)cpp" << _loader.getSchemaNamespace();

	const auto schemaNamespace = ossNamespace.str();
	std::ostringstream ossInterfaceNamespace;

	ossInterfaceNamespace << schemaNamespace << R"cpp(::object)cpp";

	const auto objectNamespace = ossInterfaceNamespace.str();

	for (const auto& interfaceType : _loader.getInterfaceTypes())
	{
		const auto headerFilename = std::string(interfaceType.cppType) + "Object.h";
		auto headerPath = (headerDir / headerFilename).string();

		{
			std::ofstream headerFile(headerPath, std::ios_base::trunc);
			IncludeGuardScope includeGuard { headerFile, headerFilename };

			headerFile << R"cpp(#include ")cpp"
					   << std::filesystem::path(_headerPath).filename().string() << R"cpp("

)cpp";

			NamespaceScope headerNamespace { headerFile, objectNamespace };

			// Output the full declaration
			headerFile << std::endl;
			outputInterfaceDeclaration(headerFile, interfaceType.cppType);
			headerFile << std::endl;

			if (_options.verbose)
			{
				files.push_back(std::move(headerPath));
			}
		}

		const auto sourceFilename = std::string(interfaceType.cppType) + "Object.cpp";
		auto sourcePath = (sourceDir / sourceFilename).string();

		{
			std::ofstream sourceFile(sourcePath, std::ios_base::trunc);

			sourceFile << R"cpp(// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// WARNING! Do not edit this file manually, your changes will be overwritten.

#include ")cpp" << headerFilename
					   << R"cpp("

#include "graphqlservice/internal/Schema.h"

#include "graphqlservice/introspection/IntrospectionSchema.h"

using namespace std::literals;

)cpp";

			NamespaceScope sourceSchemaNamespace { sourceFile, schemaNamespace };
			NamespaceScope sourceInterfaceNamespace { sourceFile, "object" };

			sourceFile << std::endl;
			outputInterfaceImplementation(sourceFile, interfaceType.cppType);
			sourceFile << std::endl;

			sourceInterfaceNamespace.exit();
			sourceFile << std::endl;

			sourceFile << R"cpp(void Add)cpp" << interfaceType.cppType
					   << R"cpp(Details(const std::shared_ptr<schema::InterfaceType>& type)cpp"
					   << interfaceType.cppType
					   << R"cpp(, const std::shared_ptr<schema::Schema>& schema)
{
)cpp";
			outputInterfaceIntrospection(sourceFile, interfaceType);
			sourceFile << R"cpp(}

)cpp";
		}

		files.push_back(std::move(sourcePath));
	}

	for (const auto& unionType : _loader.getUnionTypes())
	{
		const auto headerFilename = std::string(unionType.cppType) + "Object.h";
		auto headerPath = (headerDir / headerFilename).string();

		{
			std::ofstream headerFile(headerPath, std::ios_base::trunc);
			IncludeGuardScope includeGuard { headerFile, headerFilename };

			headerFile << R"cpp(#include ")cpp"
					   << std::filesystem::path(_headerPath).filename().string() << R"cpp("

)cpp";

			NamespaceScope headerNamespace { headerFile, objectNamespace };

			// Output the full declaration
			headerFile << std::endl;
			outputInterfaceDeclaration(headerFile, unionType.cppType);
			headerFile << std::endl;
		}

		if (_options.verbose)
		{
			files.push_back(std::move(headerPath));
		}

		const auto sourceFilename = std::string(unionType.cppType) + "Object.cpp";
		auto sourcePath = (sourceDir / sourceFilename).string();

		{
			std::ofstream sourceFile(sourcePath, std::ios_base::trunc);

			sourceFile << R"cpp(// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// WARNING! Do not edit this file manually, your changes will be overwritten.

#include ")cpp" << headerFilename
					   << R"cpp("

#include "graphqlservice/internal/Schema.h"

#include "graphqlservice/introspection/IntrospectionSchema.h"

using namespace std::literals;

)cpp";

			NamespaceScope sourceSchemaNamespace { sourceFile, schemaNamespace };
			NamespaceScope sourceUnionNamespace { sourceFile, "object" };

			sourceFile << std::endl;
			outputInterfaceImplementation(sourceFile, unionType.cppType);
			sourceFile << std::endl;

			sourceUnionNamespace.exit();
			sourceFile << std::endl;

			sourceFile << R"cpp(void Add)cpp" << unionType.cppType
					   << R"cpp(Details(const std::shared_ptr<schema::UnionType>& type)cpp"
					   << unionType.cppType
					   << R"cpp(, const std::shared_ptr<schema::Schema>& schema)
{
)cpp";
			outputUnionIntrospection(sourceFile, unionType);
			sourceFile << R"cpp(}

)cpp";
		}

		files.push_back(std::move(sourcePath));
	}

	for (const auto& objectType : _loader.getObjectTypes())
	{
		const bool isQueryType = objectType.type == queryType;
		const auto headerFilename = std::string(objectType.cppType) + "Object.h";
		auto headerPath = (headerDir / headerFilename).string();

		{
			std::ofstream headerFile(headerPath, std::ios_base::trunc);
			IncludeGuardScope includeGuard { headerFile, headerFilename };

			headerFile << R"cpp(#include ")cpp"
					   << std::filesystem::path(_headerPath).filename().string() << R"cpp("

)cpp";

			NamespaceScope headerNamespace { headerFile, objectNamespace };

			if (!_loader.isIntrospection())
			{
				if (!objectType.interfaces.empty() || !objectType.unions.empty())
				{
					NamespaceScope implementsNamespace { headerFile, R"cpp(implements)cpp" };

					headerFile << std::endl;
					outputObjectImplements(headerFile, objectType);

					implementsNamespace.exit();
					headerFile << std::endl;
				}

				// Output the stub concepts
				std::ostringstream ossConceptNamespace;

				ossConceptNamespace << R"cpp(methods::)cpp" << objectType.cppType << R"cpp(Has)cpp";

				const auto conceptNamespace = ossConceptNamespace.str();
				NamespaceScope stubNamespace { headerFile, conceptNamespace };

				outputObjectStubs(headerFile, objectType);
			}

			// Output the full declaration
			headerFile << std::endl;
			outputObjectDeclaration(headerFile, objectType, isQueryType);
			headerFile << std::endl;
		}

		if (_options.verbose)
		{
			files.push_back(std::move(headerPath));
		}

		const auto sourceFilename = std::string(objectType.cppType) + "Object.cpp";
		auto sourcePath = (sourceDir / sourceFilename).string();

		{
			std::ofstream sourceFile(sourcePath, std::ios_base::trunc);

			sourceFile << R"cpp(// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// WARNING! Do not edit this file manually, your changes will be overwritten.

#include ")cpp" << headerFilename
					   << R"cpp("
)cpp";

			std::unordered_set<std::string_view> includedObjects;

			for (const auto& field : objectType.fields)
			{
				switch (field.fieldType)
				{
					case OutputFieldType::Interface:
					case OutputFieldType::Union:
					case OutputFieldType::Object:
						if (includedObjects.insert(field.type).second)
						{
							sourceFile << R"cpp(#include ")cpp"
									   << _loader.getSafeCppName(field.type) << R"cpp(Object.h"
)cpp";
						}
						break;

					default:
						break;
				}
			}

			if (_loader.isIntrospection() || (isQueryType && !_options.noIntrospection))
			{
				sourceFile << R"cpp(
#include "graphqlservice/internal/Introspection.h"
)cpp";
			}
			else
			{
				sourceFile << R"cpp(
#include "graphqlservice/internal/Schema.h"

#include "graphqlservice/introspection/IntrospectionSchema.h"
)cpp";
			}

			if (isQueryType && !_options.noIntrospection)
			{
				sourceFile << R"cpp(
#include "graphqlservice/introspection/SchemaObject.h"
#include "graphqlservice/introspection/TypeObject.h"
)cpp";
			}

			sourceFile << R"cpp(
#include <algorithm>
#include <functional>
#include <sstream>
#include <stdexcept>
#include <unordered_map>

using namespace std::literals;

)cpp";

			NamespaceScope sourceSchemaNamespace { sourceFile, schemaNamespace };
			NamespaceScope sourceObjectNamespace { sourceFile, "object" };

			sourceFile << std::endl;
			outputObjectImplementation(sourceFile, objectType, isQueryType);
			sourceFile << std::endl;

			sourceObjectNamespace.exit();
			sourceFile << std::endl;

			sourceFile << R"cpp(void Add)cpp" << objectType.cppType
					   << R"cpp(Details(const std::shared_ptr<schema::ObjectType>& type)cpp"
					   << objectType.cppType
					   << R"cpp(, const std::shared_ptr<schema::Schema>& schema)
{
)cpp";
			outputObjectIntrospection(sourceFile, objectType);
			sourceFile << R"cpp(}

)cpp";
		}

		files.push_back(std::move(sourcePath));
	}

	return files;
}

} // namespace graphql::generator::schema