// // ResourceParser.h // snowcrash // // Created by Zdenek Nemec on 5/4/13. // Copyright (c) 2013 Apiary Inc. All rights reserved. // #ifndef SNOWCRASH_RESOURCEPARSER_H #define SNOWCRASH_RESOURCEPARSER_H #include "SectionParser.h" #include "ActionParser.h" #include "DataStructureGroupParser.h" #include "HeadersParser.h" #include "ParametersParser.h" #include "UriTemplateParser.h" #include "RegexMatch.h" namespace snowcrash { /** Nameless resource matching regex */ const char* const ResourceHeaderRegex = "^[[:blank:]]*(" HTTP_REQUEST_METHOD "[[:blank:]]+)?" URI_TEMPLATE "$"; /** Named resource matching regex */ const char* const NamedResourceHeaderRegex = "^[[:blank:]]*" SYMBOL_IDENTIFIER "[[:blank:]]+\\[" URI_TEMPLATE "]$"; /** Named endpoint matching regex */ const char* const NamedEndpointHeaderRegex = "^[[:blank:]]*" SYMBOL_IDENTIFIER "[[:blank:]]+\\[" HTTP_REQUEST_METHOD "[[:blank:]]+" URI_TEMPLATE "]$"; /** Internal type alias for Collection iterator of Resource */ typedef Collection::const_iterator ResourceIterator; /** * Resource Section processor */ template<> struct SectionProcessor : public SectionProcessorBase { static MarkdownNodeIterator processSignature(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, SectionLayout& layout, const ParseResultRef& out) { CaptureGroups captureGroups; // If Abbreviated resource section if (RegexCapture(node->text, ResourceHeaderRegex, captureGroups, 4)) { out.node.uriTemplate = captureGroups[3]; // Make this section an action if (!captureGroups[2].empty()) { return processNestedAction(node, node->parent().children(), pd, layout, out); } } else if (RegexCapture(node->text, NamedEndpointHeaderRegex, captureGroups, 5)) { out.node.name = captureGroups[1]; TrimString(out.node.name); out.node.uriTemplate = captureGroups[3]; return processNestedAction(node, node->parent().children(), pd, layout, out); } else { matchNamedResourceHeader(node, out.node); } if (pd.exportSourceMap()) { if (!out.node.uriTemplate.empty()) { out.sourceMap.uriTemplate.sourceMap = node->sourceMap; } if (!out.node.name.empty()) { out.sourceMap.name.sourceMap = node->sourceMap; } } return ++MarkdownNodeIterator(node); } static MarkdownNodeIterator processNestedSection(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, const ParseResultRef& out) { switch (pd.sectionContext()) { case ActionSectionType: return processAction(node, siblings, pd, out); case ParametersSectionType: return processParameters(node, siblings, pd, out); case ModelSectionType: case ModelBodySectionType: return processModel(node, siblings, pd, out); case HeadersSectionType: { ParseResultRef headers(out.report, out.node.headers, out.sourceMap.headers); return SectionProcessor::handleDeprecatedHeaders(node, siblings, pd, headers); } case AttributesSectionType: { // Set up named type context if (!out.node.name.empty()) { pd.namedTypeContext = out.node.name; } ParseResultRef attributes(out.report, out.node.attributes, out.sourceMap.attributes); MarkdownNodeIterator cur = AttributesParser::parse(node, siblings, pd, attributes); // Clear named type context pd.namedTypeContext.clear(); if (!out.node.name.empty()) { if (SectionProcessor::isNamedTypeDuplicate(pd.blueprint, out.node.name)) { // WARN: duplicate named type std::stringstream ss; ss << "named type with name '" << out.node.name << "' already exists"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.warnings.push_back(Warning(ss.str(), DuplicateWarning, sourceMap)); // Remove the attributes data from the AST since we are ignoring this out.node.attributes = mson::NamedType(); return cur; } attributes.node.name.symbol.literal = out.node.name; if (pd.exportSourceMap()) { attributes.sourceMap.name.sourceMap = out.sourceMap.name.sourceMap; } } return cur; } default: break; } return node; } static MarkdownNodeIterator processUnexpectedNode(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, SectionType& sectionType, const ParseResultRef& out) { if ((node->type == mdp::ParagraphMarkdownNodeType || node->type == mdp::CodeMarkdownNodeType) && (sectionType == ModelBodySectionType || sectionType == ModelSectionType)) { mdp::ByteBuffer content = CodeBlockUtility::addDanglingAsset(node, pd, sectionType, out.report, out.node.model.body); if (pd.exportSourceMap() && !content.empty()) { out.sourceMap.model.body.sourceMap.append(node->sourceMap); } // Update model in the model table as well ModelTable::iterator it = pd.modelTable.find(out.node.model.name); if (it != pd.modelTable.end()) { it->second.body = out.node.model.body; if (pd.exportSourceMap()) { pd.modelSourceMapTable[out.node.model.name].body = out.sourceMap.model.body; } } return ++MarkdownNodeIterator(node); } return SectionProcessorBase::processUnexpectedNode(node, siblings, pd, sectionType, out); } static bool isDescriptionNode(const MarkdownNodeIterator& node, SectionType sectionType) { if (SectionProcessor::actionType(node) == CompleteActionType) { return false; } return SectionProcessorBase::isDescriptionNode(node, sectionType); } static SectionType sectionType(const MarkdownNodeIterator& node) { if (node->type == mdp::HeaderMarkdownNodeType && !node->text.empty()) { CaptureGroups captureGroups; mdp::ByteBuffer subject = node->text; TrimString(subject); if (RegexMatch(subject, NamedResourceHeaderRegex) || RegexMatch(subject, NamedEndpointHeaderRegex) || RegexMatch(subject, ResourceHeaderRegex)) { return ResourceSectionType; } } return UndefinedSectionType; } static SectionType nestedSectionType(const MarkdownNodeIterator& node) { SectionType nestedType = UndefinedSectionType; // Check if parameters section nestedType = SectionProcessor::sectionType(node); if (nestedType != UndefinedSectionType) { return nestedType; } // Check if headers section nestedType = SectionProcessor::sectionType(node); if (nestedType != UndefinedSectionType) { return nestedType; } // Check if model section nestedType = SectionProcessor::sectionType(node); if (nestedType == ModelSectionType || nestedType == ModelBodySectionType) { return nestedType; } // Check if attributes section nestedType = SectionProcessor::sectionType(node); if (nestedType != UndefinedSectionType) { return nestedType; } // Check if action section nestedType = SectionProcessor::sectionType(node); if (nestedType == ActionSectionType) { // Do not consider complete actions as nested mdp::ByteBuffer method; if (SectionProcessor::actionType(node) == CompleteActionType) return UndefinedSectionType; return nestedType; } return UndefinedSectionType; } static SectionTypes upperSectionTypes() { return {ResourceGroupSectionType, ResourceSectionType, DataStructureGroupSectionType}; } static void finalize(const MarkdownNodeIterator& node, SectionParserData& pd, const ParseResultRef& out) { if (!out.node.uriTemplate.empty()) { URITemplateParser uriTemplateParser; ParsedURITemplate parsedResult; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); uriTemplateParser.parse(out.node.uriTemplate, sourceMap, parsedResult); if (!parsedResult.report.warnings.empty()) { out.report += parsedResult.report; } } // Consolidate deprecated headers into subsequent payloads if (!out.node.headers.empty()) { Collection::iterator actionIt = out.node.actions.begin(); Collection >::iterator actionSMIt = out.sourceMap.actions.collection.begin(); for (; actionIt != out.node.actions.end(); ++actionIt, ++actionSMIt) { SectionProcessor::injectDeprecatedHeaders(pd, out.node.headers, out.sourceMap.headers, actionIt->examples, actionSMIt->examples); } out.node.headers.clear(); if (pd.exportSourceMap()) { out.sourceMap.headers.collection.clear(); } } } /** * \brief Given a named resource header, retrieve the name and uriTemplate * * \param node Markdown node to process * \param resource Resource data structure */ static void matchNamedResourceHeader(const MarkdownNodeIterator& node, Resource& resource) { CaptureGroups captureGroups; if (RegexCapture(node->text, NamedResourceHeaderRegex, captureGroups, 4)) { resource.name = captureGroups[1]; TrimString(resource.name); resource.uriTemplate = captureGroups[2]; } } /** * \brief Parse the current node as an action */ static MarkdownNodeIterator processNestedAction(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, SectionLayout& layout, const ParseResultRef& out) { IntermediateParseResult action(out.report); MarkdownNodeIterator cur = ActionParser::parse(node, siblings, pd, action); out.node.actions.push_back(action.node); layout = RedirectSectionLayout; if (pd.exportSourceMap()) { out.sourceMap.actions.collection.push_back(action.sourceMap); out.sourceMap.uriTemplate.sourceMap = node->sourceMap; } return cur; } /** Process Action section */ static MarkdownNodeIterator processAction(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, const ParseResultRef& out) { IntermediateParseResult action(out.report); MarkdownNodeIterator cur = ActionParser::parse(node, siblings, pd, action); ActionIterator duplicate = SectionProcessor::findAction(out.node.actions, action.node); if (duplicate != out.node.actions.end()) { // WARN: duplicate method std::stringstream ss; ss << "action with method '" << action.node.method << "' already defined for resource '"; ss << out.node.uriTemplate << "'"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.warnings.push_back(Warning(ss.str(), DuplicateWarning, sourceMap)); } ActionIterator relationDuplicate = SectionProcessor::findRelation(out.node.actions, action.node.relation); if (relationDuplicate != out.node.actions.end()) { // WARN: duplicate relation identifier std::stringstream ss; ss << "relation identifier '" << action.node.relation.str << "' already defined for resource '" << out.node.uriTemplate << "'"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.warnings.push_back(Warning(ss.str(), DuplicateWarning, sourceMap)); } if (!action.node.parameters.empty() && action.node.uriTemplate.empty()) { checkParametersEligibility(node, pd, action.node.parameters, out); } out.node.actions.push_back(action.node); if (pd.exportSourceMap()) { out.sourceMap.actions.collection.push_back(action.sourceMap); } return cur; } /** Process Parameters section */ static MarkdownNodeIterator processParameters(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, const ParseResultRef& out) { IntermediateParseResult parameters(out.report); MarkdownNodeIterator cur = ParametersParser::parse(node, siblings, pd, parameters); if (!parameters.node.empty()) { checkParametersEligibility(node, pd, parameters.node, out); out.node.parameters.insert(out.node.parameters.end(), parameters.node.begin(), parameters.node.end()); if (pd.exportSourceMap()) { out.sourceMap.parameters.collection.insert(out.sourceMap.parameters.collection.end(), parameters.sourceMap.collection.begin(), parameters.sourceMap.collection.end()); } } return cur; } /** Process Model section */ static MarkdownNodeIterator processModel(const MarkdownNodeIterator& node, const MarkdownNodes& siblings, SectionParserData& pd, const ParseResultRef& out) { IntermediateParseResult model(out.report); MarkdownNodeIterator cur = PayloadParser::parse(node, siblings, pd, model); // Check whether there isn't a model already if (!out.node.model.name.empty()) { // WARN: Model already defined std::stringstream ss; ss << "overshadowing previous model definition for '"; if (!out.node.name.empty()) { ss << out.node.name << "(" << out.node.uriTemplate << ")"; } else { ss << out.node.uriTemplate; } ss << "' resource, a resource can be represented by a single model only"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.warnings.push_back(Warning(ss.str(), DuplicateWarning, sourceMap)); } if (model.node.name.empty()) { if (!out.node.name.empty()) { model.node.name = out.node.name; if (pd.exportSourceMap()) { model.sourceMap.name = out.sourceMap.name; } } else { // ERR: No name specified for resource model std::stringstream ss; ss << "resource model can be specified only for a named resource"; ss << ", name your resource, e.g. '# [" << out.node.uriTemplate << "]'"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.error = Error(ss.str(), ModelError, sourceMap); } } ModelTable::iterator it = pd.modelTable.find(model.node.name); if (it == pd.modelTable.end()) { pd.modelTable[model.node.name] = model.node; if (pd.exportSourceMap()) { pd.modelSourceMapTable[model.node.name] = model.sourceMap; } } else { // ERR: Model already defined std::stringstream ss; ss << "symbol '" << model.node.name << "' already defined"; mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceCharacterIndex); out.report.error = Error(ss.str(), ModelError, sourceMap); } out.node.model = model.node; if (pd.exportSourceMap()) { out.sourceMap.model = model.sourceMap; } return cur; } /** * \brief Given a list of elements, Check if a resource already exists with the given uri template * * \param elements Collection of elements * \param uri The resource uri template to be checked */ static bool isResourceDuplicate(const Elements& elements, const URITemplate& uri) { for (Elements::const_iterator it = elements.begin(); it != elements.end(); ++it) { if (it->element == Element::ResourceElement && it->content.resource.uriTemplate == uri) { return true; } } return false; } }; /** Resource Section Parser */ typedef SectionParser ResourceParser; } #endif