diff --git a/snowcrash.gyp b/snowcrash.gyp index 6c323726..abbf6a63 100644 --- a/snowcrash.gyp +++ b/snowcrash.gyp @@ -82,7 +82,10 @@ 'sources': [ 'test/test-AssetParser.cc', 'test/test-HeadersParser.cc', + 'test/test-ParameterParser.cc', + 'test/test-ParametersParser.cc', 'test/test-PayloadParser.cc', + 'test/test-ValuesParser.cc', 'test/test-Blueprint.cc', 'test/test-snowcrash.cc' ], diff --git a/src/Blueprint.h b/src/Blueprint.h index a0771210..7f4801bc 100644 --- a/src/Blueprint.h +++ b/src/Blueprint.h @@ -48,7 +48,7 @@ namespace snowcrash { /** Parameter Value */ typedef std::string Value; - + /** A generic key - value pair */ typedef std::pair KeyValuePair; diff --git a/src/ParameterDefinitonParser.h b/src/ParameterDefinitonParser.h deleted file mode 100644 index 5be4fe33..00000000 --- a/src/ParameterDefinitonParser.h +++ /dev/null @@ -1,570 +0,0 @@ -// -// ParameterDefinitonParser.h -// snowcrash -// -// Created by Zdenek Nemec on 9/1/13. -// Copyright (c) 2013 Apiary Inc. All rights reserved. -// - -#ifndef SNOWCRASH_PARAMETERDEFINITIONPARSER_H -#define SNOWCRASH_PARAMETERDEFINITIONPARSER_H - -#include -#include "BlueprintParserCore.h" -#include "Blueprint.h" -#include "RegexMatch.h" -#include "StringUtility.h" -#include "ListBlockUtility.h" -#include "SectionUtility.h" -#include "DescriptionSectionUtility.h" - -/** Parameter Value regex */ -#define PARAMETER_VALUE "`([^`]+)`" - -/** Parameter Identifier */ -#define PARAMETER_IDENTIFIER "([[:alnum:]_.-]+)" - -/** Lead in and out for comma separated values regex */ -#define CSV_LEADINOUT "[[:blank:]]*,?[[:blank:]]*" - -namespace snowcrashconst { - - /** Parameter Abbreviated definition matching regex */ - const char* const ParameterAbbrevDefinitionRegex = "^" PARAMETER_IDENTIFIER \ - "([[:blank:]]*=[[:blank:]]*`([^`]*)`[[:blank:]]*)?([[:blank:]]*\\(([^)]*)\\)[[:blank:]]*)?([[:blank:]]*\\.\\.\\.[[:blank:]]*(.*))?$"; - - /** Parameter Required matching regex */ - const char* const ParameterRequiredRegex = "^[[:blank:]]*[Rr]equired[[:blank:]]*$"; - - /** Parameter Optional matching regex */ - const char* const ParameterOptionalRegex = "^[[:blank:]]*[Oo]ptional[[:blank:]]*$"; - - /** Additonal Parameter Traits Example matching regex */ - const char* const AdditionalTraitsExampleRegex = CSV_LEADINOUT "`([^`]*)`" CSV_LEADINOUT; - - /** Additonal Parameter Traits Use matching regex */ - const char* const AdditionalTraitsUseRegex = CSV_LEADINOUT "([Oo]ptional|[Rr]equired)" CSV_LEADINOUT; - - /** Additonal Parameter Traits Type matching regex */ - const char* const AdditionalTraitsTypeRegex = CSV_LEADINOUT "([^,]*)" CSV_LEADINOUT; - - /** Parameter Values matching regex */ - const char* const ParameterValuesRegex = "^[[:blank:]]*[Vv]alues[[:blank:]]*$"; - - /** Values expected content */ - const char* const ExpectedValuesContent = "nested list of possible parameter values, one element per list item e.g. '`value`'"; -} - -namespace snowcrash { - - /** - * Classifier of internal list items, ParameterCollection context. - */ - template <> - FORCEINLINE SectionType ClassifyInternaListBlock(const BlockIterator& begin, - const BlockIterator& end) { - - - if (begin->type != ListBlockBeginType && - begin->type != ListItemBlockBeginType) - return UndefinedSectionType; - - SourceData remainingContent; - SourceData content = GetListItemSignature(begin, end, remainingContent); - - content = TrimString(content); - - if (RegexMatch(content, snowcrashconst::ParameterValuesRegex)) - return ParameterValuesSectionType; - - return UndefinedSectionType; - } - - /** Children blocks classifier */ - template <> - FORCEINLINE SectionType ClassifyChildrenListBlock(const BlockIterator& begin, - const BlockIterator& end) { - - SectionType type = ClassifyInternaListBlock(begin, end); - if (type != UndefinedSectionType) - return type; - - return UndefinedSectionType; - } - - /** - * Returns true if given block has a parameter definition signature, false otherwise. - */ - FORCEINLINE bool HasParameterDefinitionSignature(const BlockIterator& begin, - const BlockIterator& end) { - - if (begin->type != ListBlockBeginType && - begin->type != ListItemBlockBeginType) - return false; - - // Since we are too generic make sure the signature is not inner list - SectionType listSection = ClassifyInternaListBlock(begin, end); - if (listSection != UndefinedSectionType) - return false; - - // Or any other reserved keyword - if (HasParametersSignature(begin, end)) - return false; - - SourceData remainingContent; - SourceData content = GetListItemSignature(begin, end, remainingContent); - content = TrimString(content); - return RegexMatch(content, snowcrashconst::ParameterAbbrevDefinitionRegex); - } - - /** - * Block Classifier, Parameter context. - */ - template <> - FORCEINLINE SectionType ClassifyBlock(const BlockIterator& begin, - const BlockIterator& end, - const SectionType& context) { - - if (context == UndefinedSectionType) { - if (HasParameterDefinitionSignature(begin, end)) - return ParameterDefinitionSectionType; - } - else if (context == ParameterDefinitionSectionType) { - - if (begin->type == ListItemBlockEndType || - begin->type == ListBlockEndType) - return UndefinedSectionType; - - SectionType listSection = ClassifyInternaListBlock(begin, end); - if (listSection != UndefinedSectionType) - return listSection; - - if (begin->type == ListBlockBeginType) - return ForeignSectionType; // Foreign nested list-item - - if (begin->type == ListItemBlockBeginType) - return UndefinedSectionType; - } - else if (context == ParameterValuesSectionType || - context == ForeignSectionType) { - - if (begin->type == ListItemBlockEndType || - begin->type == ListBlockEndType) - return UndefinedSectionType; - - SectionType listSection = ClassifyInternaListBlock(begin, end); - if (listSection != UndefinedSectionType) - return listSection; - - return ForeignSectionType; - } - - return (context == ParameterDefinitionSectionType) ? context : UndefinedSectionType; - } - - /** - * Parameter section parser. - */ - template<> - struct SectionParser { - - static ParseSectionResult ParseSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - Parameter& parameter) { - - ParseSectionResult result = std::make_pair(Result(), cur); - switch (section.type) { - - case ParameterDefinitionSectionType: - result = HandleParmeterDefinitionSection(section, cur, parser, parameter); - break; - - case ParameterValuesSectionType: - result = HandleValuesSection(section, cur, parser, parameter); - break; - - case ForeignSectionType: - result = HandleForeignSection(section, cur, parser.sourceData); - break; - - case UndefinedSectionType: - result.second = CloseList(cur, section.bounds.second); - break; - - default: - result.first.error = UnexpectedBlockError(section, cur, parser.sourceData); - break; - } - - return result; - } - - - static void Finalize(const SectionBounds& bounds, - BlueprintParserCore& parser, - Parameter& parameter, - Result& result) {} - - /** Parse a parameter definition top-level section blocks. */ - static ParseSectionResult HandleParmeterDefinitionSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - Parameter& parameter) { - - ParseSectionResult result = std::make_pair(Result(), cur); - BlockIterator sectionCur = cur; - - // Signature - if (sectionCur == section.bounds.first) { - ProcessSignature(section, sectionCur, parser.sourceData, result.first, parameter); - result.second = SkipSignatureBlock(sectionCur, section.bounds.second); - return result; - } - - // Description - result = ParseDescriptionBlock(section, - sectionCur, - parser.sourceData, - parameter); - return result; - - } - - /** - * Retrieve and process parameter definition signature. - */ - static void ProcessSignature(const BlueprintSection& section, - const BlockIterator& cur, - const SourceData& sourceData, - Result& result, - Parameter& parameter) { - - - // Set default values - parameter.use = UndefinedParameterUse; - - // Process signature - SourceData remainingContent; - SourceData signature = GetListItemSignature(cur, section.bounds.second, remainingContent); - - TrimString(signature); - CaptureGroups captureGroups; - if (RegexCapture(signature, snowcrashconst::ParameterAbbrevDefinitionRegex, captureGroups) && - captureGroups.size() == 8) { - - // Name - parameter.name = captureGroups[1]; - TrimString(parameter.name); - - // Default value - if (!captureGroups[3].empty()) - parameter.defaultValue = captureGroups[3]; - - // Additional Attributes - if (!captureGroups[5].empty()) - ProcessSignatureAdditionalTraits(section, cur, captureGroups[5], sourceData, result, parameter); - - // Description - if (!captureGroups[7].empty()) - parameter.description = captureGroups[7]; - - if (!remainingContent.empty()) { - parameter.description += "\n"; - parameter.description += remainingContent; - parameter.description += "\n"; - } - - // Check possible required vs default clash - if (parameter.use != OptionalParameterUse && - !parameter.defaultValue.empty()) { - - // WARN: Required vs default clash - std::stringstream ss; - ss << "specifying parameter '" << parameter.name << "' as required supersedes its default value"\ - ", declare the parameter as 'optional' to specify its default value"; - - BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second); - SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData); - result.warnings.push_back(Warning(ss.str(), - LogicalErrorWarning, - sourceBlock)); - } - - } - else { - // ERR: unable to parse - BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second); - SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData); - result.error = (Error("unable to parse parameter specification", - BusinessError, - sourceBlock)); - } - } - - /** Parse additional parameter attributes from abbrev definition bracket */ - static void ProcessSignatureAdditionalTraits(const BlueprintSection& section, - const BlockIterator& cur, - const SourceData& additionalTraits, - const SourceData& sourceData, - Result& result, - Parameter& parameter) - { - - // Cherry pick example value, if any - std::string source = additionalTraits; - TrimString(source); - CaptureGroups captureGroups; - if (RegexCapture(source, snowcrashconst::AdditionalTraitsExampleRegex, captureGroups) && - captureGroups.size() > 1) { - - parameter.exampleValue = captureGroups[1]; - std::string::size_type pos = source.find(captureGroups[0]); - if (pos != std::string::npos) - source.replace(pos, captureGroups[0].length(), std::string()); - } - - // Cherry pick use attribute, if any - captureGroups.clear(); - if (RegexCapture(source, snowcrashconst::AdditionalTraitsUseRegex, captureGroups) && - captureGroups.size() > 1) { - - parameter.use = (RegexMatch(captureGroups[1], snowcrashconst::ParameterOptionalRegex)) ? OptionalParameterUse : RequiredParameterUse; - - std::string::size_type pos = source.find(captureGroups[0]); - if (pos != std::string::npos) - source.replace(pos, captureGroups[0].length(), std::string()); - } - - // Finish with type - captureGroups.clear(); - if (RegexCapture(source, snowcrashconst::AdditionalTraitsTypeRegex, captureGroups) && - captureGroups.size() > 1) { - - parameter.type = captureGroups[1]; - - std::string::size_type pos = source.find(captureGroups[0]); - if (pos != std::string::npos) - source.replace(pos, captureGroups[0].length(), std::string()); - } - - // Check whats left - TrimString(source); - if (!source.empty()) { - // WARN: Additional parameters traits warning - std::stringstream ss; - ss << "unable to parse additional parameter traits"; - ss << ", expected '([required | optional], [], [``])'"; - ss << ", e.g. '(optional, string, `Hello World`)'"; - - BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second); - SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, sourceData); - result.warnings.push_back(Warning(ss.str(), - FormattingWarning, - sourceBlock)); - - parameter.type.clear(); - parameter.exampleValue.clear(); - parameter.use = UndefinedParameterUse; - } - } - - /** Parse possible values enumeration section blocks. */ - static ParseSectionResult HandleValuesSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - Parameter& parameter) { - - ParseSectionResult result = std::make_pair(Result(), cur); - - // Check redefinition - if (!parameter.values.empty()) { - // WARN: parameter values are already defined - std::stringstream ss; - ss << "overshadowing previous 'values' definition"; - ss << " for parameter '" << parameter.name << "'"; - - BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second); - SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - RedefinitionWarning, - sourceBlock)); - } - - // Clear any previous content - parameter.values.clear(); - - // Check additional content in signature - CheckSignatureAdditionalContent(section, - cur, - parser.sourceData, - "'values:' keyword", - snowcrashconst::ExpectedValuesContent, - result.first); - - // Parse inner list of entities - BlockIterator sectionCur = SkipSignatureBlock(cur, section.bounds.second); - BlockIterator endCur = cur; - if (endCur->type == ListBlockBeginType) - ++endCur; - endCur = SkipToClosingBlock(endCur, section.bounds.second, ListItemBlockBeginType, ListItemBlockEndType); - - if (sectionCur != endCur) { - - // Iterate over list blocks, try to parse any nested lists of possible elements - for (; sectionCur != endCur; ++sectionCur) { - - if (sectionCur->type == QuoteBlockBeginType) - sectionCur = SkipToClosingBlock(sectionCur, endCur, QuoteBlockBeginType, QuoteBlockEndType); - - bool entitiesParsed = false; - if (sectionCur->type == ListBlockBeginType) { - if (parameter.values.empty()) { - - // Try to parse some values - ParseSectionResult valuesResult = ParseValuesEntities(sectionCur, - section.bounds, - parser, - parameter.values); - result.first += valuesResult.first; - sectionCur = valuesResult.second; - if (result.first.error.code != Error::OK) - return result; - - entitiesParsed = true; - } - else { - sectionCur = SkipToClosingBlock(sectionCur, endCur, ListBlockBeginType, ListBlockEndType); - } - } - - if (!entitiesParsed) { - // WARN: ignoring extraneous content - std::stringstream ss; - ss << "ignoring additional content in the 'values' attribute of the '"; - ss << parameter.name << "' parameter"; - ss << ", " << snowcrashconst::ExpectedValuesContent; - - SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - IgnoringWarning, - sourceBlock)); - } - } - } - - if (parameter.values.empty()) { - // WARN: empty definition - std::stringstream ss; - ss << "no possible values specified for parameter '" << parameter.name << "'"; - SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - EmptyDefinitionWarning, - sourceBlock)); - } - - if((!parameter.exampleValue.empty() || !parameter.defaultValue.empty()) && !parameter.values.empty()) { - CheckExampleAndDefaultValue(section, sectionCur, parser, parameter, result); - } - - endCur = CloseList(sectionCur, section.bounds.second); - result.second = endCur; - return result; - } - - /** Parse entities in values attribute */ - static ParseSectionResult ParseValuesEntities(const BlockIterator& cur, - const SectionBounds& bounds, - BlueprintParserCore& parser, - Collection::type& values) { - - ParseSectionResult result = std::make_pair(Result(), cur); - - if (cur->type != ListBlockBeginType) - return result; - - BlockIterator sectionCur = ContentBlock(cur, bounds.second); - - while (sectionCur != bounds.second && - sectionCur->type == ListItemBlockBeginType) { - - sectionCur = SkipToClosingBlock(sectionCur, bounds.second, ListItemBlockBeginType, ListItemBlockEndType); - - CaptureGroups captureGroups; - std::string content = sectionCur->content; - if (content.empty()) { - // Not inline list, map from source - content = MapSourceData(parser.sourceData, sectionCur->sourceMap); - } - - RegexCapture(content, PARAMETER_VALUE, captureGroups); - if (captureGroups.size() > 1) { - values.push_back(captureGroups[1]); - } - else { - // WARN: Ignoring unexpected content - TrimString(content); - std::stringstream ss; - ss << "ignoring the '" << content << "' element"; - ss << ", expected '`" << content << "`'"; - - SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - IgnoringWarning, - sourceBlock)); - } - - ++sectionCur; - } - - result.second = sectionCur; - return result; - } - - static void CheckExampleAndDefaultValue(const BlueprintSection& section, - const BlockIterator& cur, - const BlueprintParserCore& parser, - const Parameter& parameter, - ParseSectionResult& result) { - - bool isExampleFound = false; - bool isDefaultFound = false; - - for (Collection::const_iterator it = parameter.values.begin(); it != parameter.values.end(); ++it){ - if(parameter.exampleValue == *it) { - isExampleFound = true; - } - if(parameter.defaultValue == *it) { - isDefaultFound = true; - } - } - - if(!parameter.exampleValue.empty() && !isExampleFound) { - // WARN: missing example in values. - std::stringstream ss; - ss << "the example value '" << parameter.exampleValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values"; - SourceCharactersBlock sourceBlock = CharacterMapForBlock(cur, section.bounds.second, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - LogicalErrorWarning, - sourceBlock)); - } - - if(!parameter.defaultValue.empty() && !isDefaultFound) { - // WARN: missing default in values. - std::stringstream ss; - ss << "the default value '" << parameter.defaultValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values"; - SourceCharactersBlock sourceBlock = CharacterMapForBlock(cur, section.bounds.second, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - LogicalErrorWarning, - sourceBlock)); - } - - return; - } - - }; - - typedef BlockParser > ParameterDefinitionParser; -} - -#endif diff --git a/src/ParameterParser.h b/src/ParameterParser.h new file mode 100644 index 00000000..33ebdfcc --- /dev/null +++ b/src/ParameterParser.h @@ -0,0 +1,321 @@ +// +// ParameterParser.h +// snowcrash +// +// Created by Zdenek Nemec on 9/1/13. +// Copyright (c) 2013 Apiary Inc. All rights reserved. +// + +#ifndef SNOWCRASH_PARAMETERPARSER_H +#define SNOWCRASH_PARAMETERPARSER_H + +#include "SectionParser.h" +#include "ValuesParser.h" +#include "RegexMatch.h" +#include "StringUtility.h" + +/** Parameter Identifier */ +#define PARAMETER_IDENTIFIER "([[:alnum:]_.-]+)" + +/** Lead in and out for comma separated values regex */ +#define CSV_LEADINOUT "[[:blank:]]*,?[[:blank:]]*" + +namespace snowcrash { + + /** Parameter Abbreviated definition matching regex */ + const char* const ParameterAbbrevDefinitionRegex = "^" PARAMETER_IDENTIFIER \ + "([[:blank:]]*=[[:blank:]]*`([^`]*)`[[:blank:]]*)?([[:blank:]]*\\(([^)]*)\\)[[:blank:]]*)?([[:blank:]]*\\.\\.\\.[[:blank:]]*(.*))?$"; + + /** Parameter Required matching regex */ + const char* const ParameterRequiredRegex = "^[[:blank:]]*[Rr]equired[[:blank:]]*$"; + + /** Parameter Optional matching regex */ + const char* const ParameterOptionalRegex = "^[[:blank:]]*[Oo]ptional[[:blank:]]*$"; + + /** Additonal Parameter Traits Example matching regex */ + const char* const AdditionalTraitsExampleRegex = CSV_LEADINOUT "`([^`]*)`" CSV_LEADINOUT; + + /** Additonal Parameter Traits Use matching regex */ + const char* const AdditionalTraitsUseRegex = CSV_LEADINOUT "([Oo]ptional|[Rr]equired)" CSV_LEADINOUT; + + /** Additonal Parameter Traits Type matching regex */ + const char* const AdditionalTraitsTypeRegex = CSV_LEADINOUT "([^,]*)" CSV_LEADINOUT; + + /** Values expected content */ + const char* const ExpectedValuesContent = "nested list of possible parameter values, one element per list item e.g. '`value`'"; + + /** + * Parameter section processor + */ + template<> + struct SectionProcessor : public SectionProcessorBase { + + static MarkdownNodeIterator processSignature(const MarkdownNodeIterator& node, + SectionParserData& pd, + Report& report, + Parameter& out) { + + mdp::ByteBuffer signature, remainingContent; + signature = GetFirstLine(node->text, remainingContent); + + parseSignature(node, pd, signature, report, out); + + if (!remainingContent.empty()) { + out.description += "\n" + remainingContent + "\n"; + } + + return ++MarkdownNodeIterator(node); + } + + + static MarkdownNodeIterator processNestedSection(const MarkdownNodeIterator& node, + const MarkdownNodes& siblings, + SectionParserData& pd, + Report& report, + Parameter& out) { + + if (pd.sectionContext() != ValuesSectionType) { + return node; + } + + // Check redefinition + if (!out.values.empty()) { + // WARN: parameter values are already defined + std::stringstream ss; + ss << "overshadowing previous 'values' definition"; + ss << " for parameter '" << out.name << "'"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + RedefinitionWarning, + sourceMap)); + } + + // Clear any previous values + out.values.clear(); + + ValuesParser::parse(node, siblings, pd, report, out.values); + + if (out.values.empty()) { + // WARN: empty definition + std::stringstream ss; + ss << "no possible values specified for parameter '" << out.name << "'"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + EmptyDefinitionWarning, + sourceMap)); + } + + if ((!out.exampleValue.empty() || !out.defaultValue.empty()) && + !out.values.empty()) { + + checkExampleAndDefaultValue(node, pd, report, out); + } + + return ++MarkdownNodeIterator(node); + } + + static SectionType sectionType(const MarkdownNodeIterator& node) { + + if (node->type == mdp::ListItemMarkdownNodeType + && !node->children().empty()) { + + mdp::ByteBuffer subject = node->children().front().text; + + TrimString(subject); + + if (RegexMatch(subject, ParameterAbbrevDefinitionRegex)) { + return ParameterSectionType; + } + } + + return UndefinedSectionType; + } + + static SectionType nestedSectionType(const MarkdownNodeIterator& node) { + + return SectionProcessor::sectionType(node); + } + + static void parseSignature(const mdp::MarkdownNodeIterator& node, + SectionParserData& pd, + mdp::ByteBuffer& signature, + Report& report, + Parameter& parameter) { + + parameter.use = UndefinedParameterUse; + + TrimString(signature); + + CaptureGroups captureGroups; + + if (RegexCapture(signature, ParameterAbbrevDefinitionRegex, captureGroups) && + captureGroups.size() == 8) { + + // Name + parameter.name = captureGroups[1]; + TrimString(parameter.name); + + // Default value + if (!captureGroups[3].empty()) { + parameter.defaultValue = captureGroups[3]; + } + + // Additional attributes + if (!captureGroups[5].empty()) { + parseAdditionalTraits(node, pd, captureGroups[5], report, parameter); + } + + // Description + if (!captureGroups[7].empty()) { + parameter.description = captureGroups[7]; + } + + if (parameter.use != OptionalParameterUse && + !parameter.defaultValue.empty()) { + + // WARN: Required vs default clash + std::stringstream ss; + ss << "specifying parameter '" << parameter.name << "' as required supersedes its default value"\ + ", declare the parameter as 'optional' to specify its default value"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + LogicalErrorWarning, + sourceMap)); + } + } else { + // ERR: Unable to parse + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.error = Error("unable to parse parameter specification", + BusinessError, + sourceMap); + } + } + + static void parseAdditionalTraits(const mdp::MarkdownNodeIterator& node, + SectionParserData& pd, + mdp::ByteBuffer& traits, + Report& report, + Parameter& parameter) { + + TrimString(traits); + + CaptureGroups captureGroups; + + // Cherry pick example value, if any + if (RegexCapture(traits, AdditionalTraitsExampleRegex, captureGroups) && + captureGroups.size() > 1) { + + parameter.exampleValue = captureGroups[1]; + std::string::size_type pos = traits.find(captureGroups[0]); + + if (pos != std::string::npos) { + traits.replace(pos, captureGroups[0].length(), std::string()); + } + } + + captureGroups.clear(); + + // Cherry pick use attribute, if any + if (RegexCapture(traits, AdditionalTraitsUseRegex, captureGroups) && + captureGroups.size() > 1) { + + parameter.use = RegexMatch(captureGroups[1], ParameterOptionalRegex) ? OptionalParameterUse : RequiredParameterUse; + std::string::size_type pos = traits.find(captureGroups[0]); + + if (pos != std::string::npos) { + traits.replace(pos, captureGroups[0].length(), std::string()); + } + } + + captureGroups.clear(); + + // Finish with type + if (RegexCapture(traits, AdditionalTraitsTypeRegex, captureGroups) && + captureGroups.size() > 1) { + + parameter.type = captureGroups[1]; + std::string::size_type pos = traits.find(captureGroups[0]); + + if (pos != std::string::npos) { + traits.replace(pos, captureGroups[0].length(), std::string()); + } + } + + // Check what is left + TrimString(traits); + + if (!traits.empty()) { + // WARN: Additional parameters traits warning + std::stringstream ss; + ss << "unable to parse additional parameter traits"; + ss << ", expected '([required | optional], [], [``])'"; + ss << ", e.g. '(optional, string, `Hello World`)'"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + FormattingWarning, + sourceMap)); + + parameter.type.clear(); + parameter.exampleValue.clear(); + parameter.use = UndefinedParameterUse; + } + } + + static void checkExampleAndDefaultValue(const mdp::MarkdownNodeIterator& node, + SectionParserData& pd, + Report& report, + Parameter& parameter) { + + bool isExampleFound = false; + bool isDefaultFound = false; + + std::stringstream ss; + bool printWarning = false; + + for (Collection::iterator it = parameter.values.begin(); + it != parameter.values.end(); + ++it) { + + if (parameter.exampleValue == *it) { + isExampleFound = true; + } + + if (parameter.defaultValue == *it) { + isDefaultFound = true; + } + } + + if(!parameter.exampleValue.empty() && + !isExampleFound) { + + // WARN: missing example in values. + ss << "the example value '" << parameter.exampleValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values"; + printWarning = true; + } + + if(!parameter.defaultValue.empty() && + !isDefaultFound) { + + // WARN: missing default in values. + ss << "the default value '" << parameter.defaultValue << "' of parameter '"<< parameter.name <<"' is not in its list of expected values"; + printWarning = true; + } + + if (printWarning) { + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + LogicalErrorWarning, + sourceMap)); + } + } + }; + + /** Parameter Section Parser */ + typedef SectionParser ParameterParser; +} + +#endif diff --git a/src/ParametersParser.h b/src/ParametersParser.h index 24215d78..71e7970b 100644 --- a/src/ParametersParser.h +++ b/src/ParametersParser.h @@ -9,244 +9,135 @@ #ifndef SNOWCRASH_PARAMETERSPARSER_H #define SNOWCRASH_PARAMETERSPARSER_H -#include -#include "BlueprintParserCore.h" -#include "Blueprint.h" +#include "SectionParser.h" +#include "ParameterParser.h" #include "RegexMatch.h" #include "StringUtility.h" -#include "BlockUtility.h" -#include "ParameterDefinitonParser.h" +#include "BlueprintUtility.h" + +namespace snowcrash { -namespace snowcrashconst { - /** Parameters matching regex */ const char* const ParametersRegex = "^[[:blank:]]*[Pp]arameters?[[:blank:]]*$"; - /** Expected parameters content */ - const char* const ExpectedParametersContent = "a nested list of parameters, one parameter per list item"; - /** No parameters specified message */ const char* const NoParametersMessage = "no parameters specified, expected a nested list of parameters, one parameter per list item"; -} -namespace snowcrash { - /** Internal type alias for Collection of Paramaeter */ - typedef Collection::type ParameterCollection; - - - /** Finds a parameter inside a parameters collection */ - FORCEINLINE ParameterCollection::iterator FindParameter(ParameterCollection& parameters, - const Parameter& parameter) { - return std::find_if(parameters.begin(), - parameters.end(), - std::bind2nd(MatchName(), parameter)); - } - - /** - * Returns true if given block has parameters signature, false otherwise. - */ - FORCEINLINE bool HasParametersSignature(const BlockIterator& begin, - const BlockIterator& end) { + typedef Collection::type Parameters; - if (begin->type != ListBlockBeginType && - begin->type != ListItemBlockBeginType) - return false; - - SourceData remainingContent; - SourceData content = GetListItemSignature(begin, end, remainingContent); - TrimString(content); - return RegexMatch(content, snowcrashconst::ParametersRegex); - } - - /** Children List Block Classifier, ParameterCollection context. */ - template <> - FORCEINLINE SectionType ClassifyChildrenListBlock(const BlockIterator& begin, - const BlockIterator& end){ - // TODO: - return UndefinedSectionType; - } - - /** Block Classifier, ParameterCollection context. */ - template <> - FORCEINLINE SectionType ClassifyBlock(const BlockIterator& begin, - const BlockIterator& end, - const SectionType& context) { - - if (context == UndefinedSectionType) { - if (HasParametersSignature(begin, end)) - return ParametersSectionType; - } - else if (context == ParametersSectionType) { - - if (begin->type == ListItemBlockEndType || - begin->type == ListBlockEndType) - return UndefinedSectionType; - - if (HasParameterDefinitionSignature(begin, end)) - return ParameterDefinitionSectionType; - - if (begin->type == ListBlockBeginType) - return ForeignSectionType; // Foreign nested list-item - - if (begin->type == ListItemBlockBeginType) - return UndefinedSectionType; - } - else if (context == ParameterDefinitionSectionType || - context == ForeignSectionType) { - - if (begin->type == ListItemBlockEndType || - begin->type == ListBlockEndType) - return UndefinedSectionType; - - if (HasParameterDefinitionSignature(begin, end)) - return ParameterDefinitionSectionType; - - return ForeignSectionType; - } - - return (context == ParametersSectionType) ? context : UndefinedSectionType; - } + typedef Collection::iterator ParameterIterator; /** - * Parameters section parser. + * Parameters section processor */ template<> - struct SectionParser { - - static ParseSectionResult ParseSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - ParameterCollection& parameters) { - - ParseSectionResult result = std::make_pair(Result(), cur); - switch (section.type) { - case ParametersSectionType: - result = HandleParmetersSection(section, cur, parser, parameters); - break; - - case ParameterDefinitionSectionType: - result = HandleParmeterDefinitionSection(section, cur, parser, parameters); - break; - - case ForeignSectionType: - result = HandleForeignSection(section, cur, parser.sourceData); - break; - - case UndefinedSectionType: - result.second = CloseList(cur, section.bounds.second); - break; - - default: - result.first.error = UnexpectedBlockError(section, cur, parser.sourceData); - break; + struct SectionProcessor : public SectionProcessorBase { + + static MarkdownNodeIterator processSignature(const MarkdownNodeIterator& node, + SectionParserData& pd, + Report& report, + Parameters& out) { + + mdp::ByteBuffer remainingContent; + + GetFirstLine(node->text, remainingContent); + + if (!remainingContent.empty()) { + + // WARN: Extra content in parameters section + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning("additional content in parameters sections", + IgnoringWarning, + sourceMap)); } - - return result; + + return ++MarkdownNodeIterator(node); } - - static void Finalize(const SectionBounds& bounds, - BlueprintParserCore& parser, - ParameterCollection& parameters, - Result& result) {} - - /** - * \brief Parse Parameters top-level section blocks. - * \param section Actual section being parsed. - * \param cur The actual position within Markdown block buffer. - * \param parser Parser's instance. - * \param payload An output buffer to write parameters into. - * \return A block parser section result. - */ - static ParseSectionResult HandleParmetersSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - ParameterCollection& parameters) { - - ParseSectionResult result = std::make_pair(Result(), cur); - BlockIterator sectionCur = cur; - - // Signature - if (sectionCur == section.bounds.first) { - - CheckSignatureAdditionalContent(section, - sectionCur, - parser.sourceData, - "'parameters' keyword", - snowcrashconst::ExpectedParametersContent, - result.first); - result.second = SkipSignatureBlock(sectionCur, section.bounds.second); - return result; - } - - // Unexpected description - if (sectionCur->type == QuoteBlockBeginType) { - sectionCur = SkipToClosingBlock(sectionCur, section.bounds.second, QuoteBlockBeginType, QuoteBlockEndType); - } - else if (sectionCur->type == ListBlockBeginType) { - sectionCur = SkipToClosingBlock(sectionCur, section.bounds.second, ListBlockBeginType, ListItemBlockEndType); - } - - if (!CheckCursor(section, sectionCur, parser.sourceData, result.first)) - return result; - - // WARN: on ignoring additional content - std::stringstream ss; - ss << "ignoring additional content in the 'parameters' definition, expected " << snowcrashconst::ExpectedParametersContent; - - SourceCharactersBlock sourceBlock = CharacterMapForBlock(sectionCur, cur, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - IgnoringWarning, - sourceBlock)); - - if (sectionCur != section.bounds.second) - result.second = ++sectionCur; - - return result; + + static MarkdownNodeIterator processDescription(const MarkdownNodeIterator& node, + SectionParserData& pd, + Report& report, + Parameters& out) { + + return node; } - - /** Parse a parameter definition top-level section blocks. */ - static ParseSectionResult HandleParmeterDefinitionSection(const BlueprintSection& section, - const BlockIterator& cur, - BlueprintParserCore& parser, - ParameterCollection& parameters) { + + static MarkdownNodeIterator processNestedSection(const MarkdownNodeIterator& node, + const MarkdownNodes& siblings, + SectionParserData& pd, + Report& report, + Parameters& out) { + + if (pd.sectionContext() != ParameterSectionType) { + return node; + } + Parameter parameter; - ParseSectionResult result = ParameterDefinitionParser::Parse(cur, - section.bounds.second, - section, - parser, - parameter); - if (result.first.error.code != Error::OK) - return result; - - // Check duplicates - if (!parameters.empty()) { - ParameterCollection::iterator duplicate = FindParameter(parameters, parameter); - if (duplicate != parameters.end()) { + ParameterParser::parse(node, siblings, pd, report, parameter); + + if (!out.empty()) { + + ParameterIterator duplicate = FindParameter(out, parameter); + + if (duplicate != out.end()) { // WARN: Parameter already defined std::stringstream ss; ss << "overshadowing previous parameter '" << parameter.name << "' definition"; - BlockIterator nameBlock = ListItemNameBlock(cur, section.bounds.second); - SourceCharactersBlock sourceBlock = CharacterMapForBlock(nameBlock, cur, section.bounds, parser.sourceData); - result.first.warnings.push_back(Warning(ss.str(), - RedefinitionWarning, - sourceBlock)); - - // Erase origan duplicate - parameters.erase(duplicate); + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + RedefinitionWarning, + sourceMap)); + + out.erase(duplicate); + } + } + + out.push_back(parameter); + + return ++MarkdownNodeIterator(node); + } + + static bool isDescriptionNode(const MarkdownNodeIterator& node, + SectionType sectionType) { + + return false; + } + + static SectionType sectionType(const MarkdownNodeIterator& node) { + + if (node->type == mdp::ListItemMarkdownNodeType + && !node->children().empty()) { + + mdp::ByteBuffer subject = node->children().front().text; + + if (RegexMatch(subject, ParametersRegex)) { + return ParametersSectionType; } } - - parameters.push_back(parameter); - - return result; + + return UndefinedSectionType; + } + + static SectionType nestedSectionType(const MarkdownNodeIterator& node) { + + return SectionProcessor::sectionType(node); + } + + /** Finds a parameter inside a parameters collection */ + static ParameterIterator FindParameter(Parameters& parameters, + const Parameter& parameter) { + + return std::find_if(parameters.begin(), + parameters.end(), + std::bind2nd(MatchName(), parameter)); } }; - - typedef BlockParser > ParametersParser; -} + /** Parameters Section parser */ + typedef SectionParser ParametersParser; +} #endif diff --git a/src/Section.h b/src/Section.h index a8ee73a1..daa116d8 100644 --- a/src/Section.h +++ b/src/Section.h @@ -37,8 +37,9 @@ namespace snowcrash { HeadersSectionType, /// < Headers ForeignSectionType, /// < Foreign, unexpected section ParametersSectionType, /// < Parameters - ParameterDefinitionSectionType, /// < One Parameter definition - ParameterValuesSectionType /// < Parameter value enumeration + ParameterSectionType, /// < One Parameter definition + ValuesSectionType, /// < Value enumeration + ValueSectionType /// < One Value }; /** \return Human readable name for given %SectionType */ diff --git a/src/SectionProcessor.h b/src/SectionProcessor.h index 0d776395..54d02782 100644 --- a/src/SectionProcessor.h +++ b/src/SectionProcessor.h @@ -94,6 +94,17 @@ namespace snowcrash { SectionType& lastSectionType, Report& report, T& out) { + + // WARN: Ignoring unexpected node + std::stringstream ss; + + ss << "ignoring unrecognized block"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + IgnoringWarning, + sourceMap)); + return ++MarkdownNodeIterator(node); } @@ -115,6 +126,7 @@ namespace snowcrash { /** \return True if the node is unexpected in the current context */ static bool isUnexpectedNode(const MarkdownNodeIterator& node, SectionType sectionType) { + return !RecognizeSection(node); } diff --git a/src/ValuesParser.h b/src/ValuesParser.h new file mode 100644 index 00000000..497dd882 --- /dev/null +++ b/src/ValuesParser.h @@ -0,0 +1,121 @@ +// +// ValuesParser.h +// snowcrash +// +// Created by Pavan Kumar Sunkara on 6/12/14 +// Copyright (c) 2014 Apiary Inc. All rights reserved. +// + +#ifndef SNOWCRASH_VALUESPARSER_H +#define SNOWCRASH_VALUESPARSER_H + +#include "SectionParser.h" +#include "RegexMatch.h" +#include "StringUtility.h" + +/** Parameter Value regex */ +#define PARAMETER_VALUE "`([^`]+)`" + +namespace snowcrash { + + /** Parameter Values */ + typedef Collection::type Values; // TODO: Move this into core later + + /** Parameter Values matching regex */ + const char* const ValuesRegex = "^[[:blank:]]*[Vv]alues[[:blank:]]*$"; + + /** + * Values section processor + */ + template<> + struct SectionProcessor : public SectionProcessorBase { + + static MarkdownNodeIterator processNestedSection(const MarkdownNodeIterator& node, + const MarkdownNodes& siblings, + SectionParserData& pd, + Report& report, + Values& out) { + + if (pd.sectionContext() == ValueSectionType) { + + mdp::ByteBuffer content = node->children().front().text; + CaptureGroups captureGroups; + + RegexCapture(content, PARAMETER_VALUE, captureGroups); + + if (captureGroups.size() > 1) { + out.push_back(captureGroups[1]); + } else { + TrimString(content); + + // WARN: Ignoring the unexpected param value + std::stringstream ss; + ss << "ignoring the '" << content << "' element"; + ss << ", expected '`" << content << "`'"; + + mdp::CharactersRangeSet sourceMap = mdp::BytesRangeSetToCharactersRangeSet(node->sourceMap, pd.sourceData); + report.warnings.push_back(Warning(ss.str(), + IgnoringWarning, + sourceMap)); + } + + return ++MarkdownNodeIterator(node); + } + + return node; + } + + static MarkdownNodeIterator processDescription(const MarkdownNodeIterator& node, + SectionParserData& pd, + Report& report, + Values& out) { + + return node; + } + + static bool isDescriptionNode(const MarkdownNodeIterator& node, + SectionType sectionType) { + + return false; + } + + static SectionType sectionType(const MarkdownNodeIterator& node) { + + if (node->type == mdp::ListItemMarkdownNodeType + && !node->children().empty()) { + + mdp::ByteBuffer subject = node->children().front().text; + TrimString(subject); + + if (RegexMatch(subject, ValuesRegex)) { + return ValuesSectionType; + } + } + + return UndefinedSectionType; + } + + static SectionType nestedSectionType(const MarkdownNodeIterator& node) { + + if (node->type == mdp::ListItemMarkdownNodeType + && !node->children().empty()) { + + mdp::ByteBuffer subject = node->children().front().text; + TrimString(subject); + + if (node->children().size() == 1 && + !subject.empty()) { + + return ValueSectionType; + } + } + + return UndefinedSectionType; + } + }; + + /** Parameter Section Parser */ + typedef SectionParser ValuesParser; +} + +#endif diff --git a/test/test-AssetParser.cc b/test/test-AssetParser.cc index a11bb20e..e105fd6a 100644 --- a/test/test-AssetParser.cc +++ b/test/test-AssetParser.cc @@ -64,39 +64,6 @@ TEST_CASE("parse schema asset", "[asset]") REQUIRE(asset == "Dolor Sit Amet\n"); } -// TODO: This test is for the payload parser instead. -/* -TEST_CASE("Parse body asset followed by other blocks", "[payload][dangling]") -{ - mdp::ByteBuffer source = BodyAssetFixture; - source += \ - "\n"\ - "Hello World!\n"; - - mdp::MarkdownParser markdownParser; - mdp::MarkdownNode markdownAST; - markdownParser.parse(source, markdownAST); - - REQUIRE(!markdownAST.children().empty()); - - Blueprint bp; - ParserData pd(0, source, bp); - Asset asset; - Report report; - MarkdownNodeIterator cur = AssetParser::parse(markdownAST.children().begin(), - markdownAST.children(), - pd, - report, - asset); - - REQUIRE(report.error.code == Error::OK); - REQUIRE(report.warnings.size() == 1); - // TODO: Test warning code - - REQUIRE(asset == "Lorem Ipsum\n\nHello World!\n"); -} -*/ - TEST_CASE("Foreign block inside", "[asset]") { mdp::ByteBuffer source = BodyAssetFixture; diff --git a/test/test-ParameterDefinitonParser.cc b/test/test-ParameterDefinitonParser.cc deleted file mode 100644 index 409bacf4..00000000 --- a/test/test-ParameterDefinitonParser.cc +++ /dev/null @@ -1,635 +0,0 @@ -// -// test-ParameterDefinitonParser.cc -// snowcrash -// -// Created by Zdenek Nemec on 9/1/13. -// Copyright (c) 2013 Apiary Inc. All rights reserved. -// - -#include -#include "catch.hpp" -#include "Fixture.h" -#include "ParametersParser.h" -#include "Parser.h" - -using namespace snowcrash; -using namespace snowcrashtest; - -MarkdownBlock::Stack snowcrashtest::CanonicalParameterDefinitionFixture() -{ - //R"( - //+ id = `1234` (optional, number, `0000`) - // - // Lorem ipsum. - // - // + Values - // + `1234` - // + `0000` - // + `beef` - //)"; - - MarkdownBlock::Stack markdown; - - // id BEGIN - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "id = `1234` (optional, number, `0000`)", 0, MakeSourceDataBlock(1, 1))); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Lorem ipsum.", 0, MakeSourceDataBlock(2, 1))); - - // traits BEGIN - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - - // Values BEGIN - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, "`1234`\n", 0, MakeSourceDataBlock(7, 1))); - - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, "`0000`\n", 0, MakeSourceDataBlock(8, 1))); - - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, "`beef`\n", 0, MakeSourceDataBlock(9, 1))); - - // Values END - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0,MakeSourceDataBlock(10, 1))); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, "Values\n", 0, MakeSourceDataBlock(11, 1))); - - // traits END - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0,MakeSourceDataBlock(12, 1))); - - // id END - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(13, 1))); - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0,MakeSourceDataBlock(14, 1))); - - return markdown; -} - -TEST_CASE("Parameter definition block classifier", "[parameter_definition][classifier][block]") -{ - MarkdownBlock::Stack markdown = CanonicalParameterDefinitionFixture(); - - REQUIRE(markdown.size() == 18); - - BlockIterator cur = markdown.begin(); - - // ListBlockBeginType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == ParameterDefinitionSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterDefinitionSectionType) == ForeignSectionType); - - ++cur; // ListItemBlockBeginType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == ParameterDefinitionSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterDefinitionSectionType) == UndefinedSectionType); - - ++cur; // ParagraphBlockType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == UndefinedSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterDefinitionSectionType) == ParameterDefinitionSectionType); - - ++cur; // ParagraphBlockType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == UndefinedSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterDefinitionSectionType) == ParameterDefinitionSectionType); - - ++cur; // type trait BEGIN - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == UndefinedSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterDefinitionSectionType) == ParameterValuesSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParameterValuesSectionType) == ParameterValuesSectionType); -} - -TEST_CASE("Parse canonical parameter definition", "[parameter_definition][block]") -{ - MarkdownBlock::Stack markdown = CanonicalParameterDefinitionFixture(); - Parameter parameter; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParameterDefinitionParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameter); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - CHECK(std::distance(blocks.begin(), result.second) == 18); - - REQUIRE(parameter.name == "id"); - REQUIRE(parameter.description == "2"); - REQUIRE(parameter.type == "number"); - REQUIRE(parameter.use == OptionalParameterUse); - REQUIRE(parameter.defaultValue == "1234"); - REQUIRE(parameter.exampleValue == "0000"); - REQUIRE(parameter.values.size() == 3); - REQUIRE(parameter.values[0] == "1234"); - REQUIRE(parameter.values[1] == "0000"); - REQUIRE(parameter.values[2] == "beef"); -} - -TEST_CASE("Parse canonical definition followed by another definition", "[parameter_definition][block]") -{ - MarkdownBlock::Stack markdown = CanonicalParameterDefinitionFixture(); - - MarkdownBlock::Stack parameterBlocks; - parameterBlocks.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - parameterBlocks.push_back(MarkdownBlock(ParagraphBlockType, "additional_parameter", 0, MakeSourceDataBlock(1, 1))); - parameterBlocks.push_back(MarkdownBlock(ParagraphBlockType, "Hello World", 0, MakeSourceDataBlock(2, 1))); - parameterBlocks.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, MakeSourceDataBlock(3, 1))); - - MarkdownBlock::Stack::iterator it = markdown.end(); - --it; - markdown.insert(it, parameterBlocks.begin(), parameterBlocks.end()); - - - Parameter parameter; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParameterDefinitionParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameter); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - CHECK(std::distance(blocks.begin(), result.second) == 17); - - REQUIRE(parameter.name == "id"); - REQUIRE(parameter.description == "2"); -} - -TEST_CASE("Parse canonical definition followed by ilegal one", "[parameter_definition][block]") -{ - MarkdownBlock::Stack markdown = CanonicalParameterDefinitionFixture(); - - MarkdownBlock::Stack parameterBlocks; - parameterBlocks.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - parameterBlocks.push_back(MarkdownBlock(ListItemBlockBeginType, "i:legal", 0, MakeSourceDataBlock(1, 1))); - - MarkdownBlock::Stack::iterator it = markdown.end(); - --it; - markdown.insert(it, parameterBlocks.begin(), parameterBlocks.end()); - - Parameter parameter; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParameterDefinitionParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameter); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - CHECK(std::distance(blocks.begin(), result.second) == 17); - - REQUIRE(parameter.name == "id"); - REQUIRE(parameter.description == "2"); -} - -TEST_CASE("Parse ilegal parameter trait at the begining", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{4} - //+ Parameters - // + 4 - // + ilegal - // - //"); - const std::string blueprintSource = \ - "# /1/{4}\n"\ - "+ Parameters\n"\ - " + 4\n"\ - " + ilegal\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); -} - -TEST_CASE("Warn superfluous content in values attribute", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1 - //+ Parameters - // + id - // + Values - // xx - // - // + `Hello` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id\n"\ - " + Values\n"\ - " extra-1\n"\ - "\n"\ - " + `Hello`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values[0] == "Hello"); -} - -TEST_CASE("Warn about illegal entities in values attribute", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id - // + Values - // + `Hello` - // + ilegal - // + `Ahoy` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id\n"\ - " + Values\n"\ - " + `Hello`\n"\ - " + ilegal\n"\ - " + `Ahoy`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.size() == 2); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values[0] == "Hello"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values[1] == "Ahoy"); -} - -TEST_CASE("Warn when re-setting the values attribute", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id - // + Values - // + `Ahoy` - // + Values - // + `Hello` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id\n"\ - " + Values\n"\ - " + `Ahoy`\n"\ - " + Values\n"\ - " + `Hello`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == RedefinitionWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values[0] == "Hello"); -} - -TEST_CASE("Warn when there are no values in the values attribute", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id - // + Values - // - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id\n"\ - " + Values\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == EmptyDefinitionWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.empty()); -} - -TEST_CASE("Parse full abbreviated syntax", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /machine{?limit} - //+ Parameters - // + limit = `20` (optional, number, `42`) ... This is a limit - //"); - const std::string blueprintSource = \ - "# /machine{?limit}\n"\ - "+ Parameters\n"\ - " + limit = `20` (optional, number, `42`) ... This is a limit\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.empty()); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "limit"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].description == "This is a limit" ); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue == "20"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].exampleValue == "42"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].type == "number"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].use == OptionalParameterUse); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.empty()); -} - -TEST_CASE("Warn on error in abbreviated syntax attribute bracket", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /machine{?limit} - //+ Parameters - // + limit (string1, string2, string3) ... This is a limit - //"); - const std::string blueprintSource = \ - "# /machine{?limit}\n"\ - "+ Parameters\n"\ - " + limit (string1, string2, string3) ... This is a limit\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == FormattingWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "limit"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].description == "This is a limit" ); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].exampleValue.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].type.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].use == UndefinedParameterUse); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].values.empty()); -} - -TEST_CASE("Warn about required vs default clash", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id = `42` (required) - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id = `42` (required)\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == LogicalErrorWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].use == RequiredParameterUse); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue == "42"); -} - -TEST_CASE("Warn about implicit required vs default clash", "[parameter_definition][source]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id = `42` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id = `42`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == LogicalErrorWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].use == UndefinedParameterUse); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue == "42"); -} - - -TEST_CASE("Unrecognized 'values' keyword", "[parameter_definition][issue][#44][source]") -{ - // Blueprint in question: - //R"( - //FORMAT: X-1A - //HOST: xxxxxxxxxxxxxxxxxxxxxxx - // - //# A very long API name - // - //# Resource [/1/{param}] - // - //## GET - // - //+ Parameters - // + param - // + Values: - // + `lorem` - // - //+ Response 204 - //"); - const std::string blueprintSource = \ - "FORMAT: X-1A\n"\ - "HOST: xxxxxxxxxxxxxxxxxxxxxxx\n"\ - "\n"\ - "# A very long API name\n"\ - "\n"\ - "# Resource [/1/{param}]\n"\ - "\n"\ - "## GET\n"\ - "\n"\ - "+ Parameters\n"\ - " + param\n"\ - " + Values:\n"\ - " + `lorem`\n"\ - "\n"\ - "+ Response 204\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(result.warnings[0].location.size() == 2); - REQUIRE(result.warnings[0].location[0].location == 134); - REQUIRE(result.warnings[0].location[0].length == 10); - REQUIRE(result.warnings[0].location[1].location == 152); - REQUIRE(result.warnings[0].location[1].length == 14); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].name == "param"); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].use == UndefinedParameterUse); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].values.empty()); -} - -TEST_CASE("warn missing example item in values", "[parameters][issue][#67]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id = `Value2` (optional, string, `Value1`); - // + Values - // + `Value2` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id = `Value2` (optional, string, `Value1`)\n"\ - " + Values\n"\ - " + `Value2`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == LogicalErrorWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].exampleValue == "Value1"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue == "Value2"); -} - -TEST_CASE("warn missing default value in values", "[parameters][issue][#67]") -{ - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id = `Value1` (optional, string, `Value2`); - // + Values - // + `Value2` - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - " + id = `Value1` (optional, string, `Value2`)\n"\ - " + Values\n"\ - " + `Value2`\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == LogicalErrorWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].exampleValue == "Value2"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].defaultValue == "Value1"); -} \ No newline at end of file diff --git a/test/test-ParameterParser.cc b/test/test-ParameterParser.cc new file mode 100644 index 00000000..3440ea7b --- /dev/null +++ b/test/test-ParameterParser.cc @@ -0,0 +1,258 @@ +// +// test-ParameterParser.cc +// snowcrash +// +// Created by Zdenek Nemec on 9/1/13. +// Copyright (c) 2013 Apiary Inc. All rights reserved. +// + +#include "snowcrashtest.h" +#include "ParameterParser.h" + +using namespace snowcrash; +using namespace snowcrashtest; + +const mdp::ByteBuffer ParameterFixture = \ +"+ id = `1234` (optional, number, `0000`)\n\n"\ +" Lorem ipsum\n\n"\ +" + Values\n"\ +" + `1234`\n"\ +" + `0000`\n"\ +" + `beef`\n"; + +TEST_CASE("Recognize parameter definition signature", "[parameter]") +{ + mdp::MarkdownParser markdownParser; + mdp::MarkdownNode markdownAST; + markdownParser.parse(ParameterFixture, markdownAST); + + REQUIRE(!markdownAST.children().empty()); + REQUIRE(SectionProcessor::sectionType(markdownAST.children().begin()) == ParameterSectionType); +} + +TEST_CASE("Parse canonical parameter definition", "[parameter]") +{ + Parameter parameter; + Report report; + SectionParserHelper::parse(ParameterFixture, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + CHECK(report.warnings.empty()); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.description == "Lorem ipsum\n"); + REQUIRE(parameter.type == "number"); + REQUIRE(parameter.use == OptionalParameterUse); + REQUIRE(parameter.defaultValue == "1234"); + REQUIRE(parameter.exampleValue == "0000"); + REQUIRE(parameter.values.size() == 3); + REQUIRE(parameter.values[0] == "1234"); + REQUIRE(parameter.values[1] == "0000"); + REQUIRE(parameter.values[2] == "beef"); +} + +TEST_CASE("Warn when re-setting the values attribute", "[parameter]") +{ + mdp::ByteBuffer source = \ + "+ id\n"\ + " + Values\n"\ + " + `Ahoy`\n"\ + " + Values\n"\ + " + `Hello`\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == RedefinitionWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.values.size() == 1); + REQUIRE(parameter.values[0] == "Hello"); +} + +TEST_CASE("Warn when there are no values in the values attribute", "[parameter]") +{ + mdp::ByteBuffer source = \ + "+ id\n"\ + " + Values\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == EmptyDefinitionWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.values.empty()); +} + +TEST_CASE("Parse full abbreviated syntax", "[parameter]") +{ + mdp::ByteBuffer source = "+ limit = `20` (optional, number, `42`) ... This is a limit\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + CHECK(report.warnings.empty()); + + REQUIRE(parameter.name == "limit"); + REQUIRE(parameter.description == "This is a limit" ); + REQUIRE(parameter.defaultValue == "20"); + REQUIRE(parameter.exampleValue == "42"); + REQUIRE(parameter.type == "number"); + REQUIRE(parameter.use == OptionalParameterUse); + REQUIRE(parameter.values.empty()); +} + +TEST_CASE("Warn on error in abbreviated syntax attribute bracket", "[parameter]") +{ + mdp::ByteBuffer source = "+ limit (string1, string2, string3) ... This is a limit\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == FormattingWarning); + + REQUIRE(parameter.name == "limit"); + REQUIRE(parameter.description == "This is a limit" ); + REQUIRE(parameter.defaultValue.empty()); + REQUIRE(parameter.exampleValue.empty()); + REQUIRE(parameter.type.empty()); + REQUIRE(parameter.use == UndefinedParameterUse); + REQUIRE(parameter.values.empty()); +} + +TEST_CASE("Warn about required vs default clash", "[parameter]") +{ + mdp::ByteBuffer source = "+ id = `42` (required)\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == LogicalErrorWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.use == RequiredParameterUse); + REQUIRE(parameter.defaultValue == "42"); +} + +TEST_CASE("Warn about implicit required vs default clash", "[parameter_definition][source]") +{ + mdp::ByteBuffer source = "+ id = `42`\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == LogicalErrorWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.use == UndefinedParameterUse); + REQUIRE(parameter.defaultValue == "42"); +} + +TEST_CASE("Unrecognized 'values' keyword", "[parameter]") +{ + mdp::ByteBuffer source = \ + "+ param\n"\ + " + Values:\n"\ + " + `lorem`\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.empty()); + + REQUIRE(parameter.name == "param"); + REQUIRE(parameter.description == "+ Values:\n + `lorem`\n"); + REQUIRE(parameter.use == UndefinedParameterUse); + REQUIRE(parameter.values.empty()); +} + +TEST_CASE("warn missing example item in values", "[parameter]") +{ + mdp::ByteBuffer source = \ + "+ id = `Value2` (optional, string, `Value1`)\n"\ + " + Values\n"\ + " + `Value2`\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == LogicalErrorWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.exampleValue == "Value1"); + REQUIRE(parameter.defaultValue == "Value2"); +} + +TEST_CASE("warn missing default value in values", "[parameter]") +{ + mdp::ByteBuffer source = \ + "+ id = `Value1` (optional, string, `Value2`)\n"\ + " + Values\n"\ + " + `Value2`\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == LogicalErrorWarning); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.exampleValue == "Value2"); + REQUIRE(parameter.defaultValue == "Value1"); +} + +TEST_CASE("Parse parameters with dot in its name", "[parameter]") +{ + mdp::ByteBuffer source = "+ product.id ... Hello\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.empty()); + + REQUIRE(parameter.name == "product.id"); + REQUIRE(parameter.description == "Hello"); +} + +TEST_CASE("Parentheses in parameter description ", "[parameter]") +{ + mdp::ByteBuffer source = "+ id (string) ... lorem (ipsum)\n"; + + Parameter parameter; + Report report; + SectionParserHelper::parse(source, ParameterSectionType, report, parameter); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.empty()); + + REQUIRE(parameter.name == "id"); + REQUIRE(parameter.type == "string"); + REQUIRE(parameter.description == "lorem (ipsum)"); +} diff --git a/test/test-ParametersParser.cc b/test/test-ParametersParser.cc index eca9c226..873f0b27 100644 --- a/test/test-ParametersParser.cc +++ b/test/test-ParametersParser.cc @@ -6,458 +6,146 @@ // Copyright (c) 2013 Apiary Inc. All rights reserved. // -#include -#include "catch.hpp" -#include "Fixture.h" -#include "Parser.h" +#include "snowcrashtest.h" #include "ParametersParser.h" using namespace snowcrash; using namespace snowcrashtest; -MarkdownBlock::Stack snowcrashtest::CanonicalParametersFixture() +const mdp::ByteBuffer ParametersFixture = \ +"+ Parameters\n"\ +" + id = `1234` (optional, number, `0000`)\n\n"\ +" Lorem ipsum\n"\ +" + Values\n"\ +" + `1234`\n"\ +" + `0000`\n"\ +" + `beef`\n"\ +" + name\n"; + +TEST_CASE("Recognize Parameters section block", "[parameters]") { - //R"( - //+ Parameters - // - // - //)"; - - MarkdownBlock::Stack markdown; - // Parameters BEGIN - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Parameters", 0, MakeSourceDataBlock(0, 1))); - - // Inject parameter definiton - MarkdownBlock::Stack parameterDefinition = CanonicalParameterDefinitionFixture(); - markdown.insert(markdown.end(), parameterDefinition.begin(), parameterDefinition.end()); - - parameterDefinition[2].content = "limit"; - markdown.insert(markdown.end(), parameterDefinition.begin(), parameterDefinition.end()); - - // Parameters END - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(15, 1))); - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0,MakeSourceDataBlock(16, 1))); - - return markdown; -} + mdp::MarkdownParser markdownParser; + mdp::MarkdownNode markdownAST; + markdownParser.parse(ParametersFixture, markdownAST); -TEST_CASE("Parameters block classifier", "[parameters][classifier]") -{ - MarkdownBlock::Stack markdown = CanonicalParametersFixture(); - - REQUIRE(markdown.size() == 41); - - BlockIterator cur = markdown.begin(); - - // ListBlockBeginType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == ParametersSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParametersSectionType) == ForeignSectionType); - - ++cur; // ListItemBlockBeginType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == ParametersSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParametersSectionType) == UndefinedSectionType); - - ++cur; // ParagraphBlockType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == UndefinedSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParametersSectionType) == ParametersSectionType); - - ++cur; // ListBlockBeginType - REQUIRE(ClassifyBlock(cur, markdown.end(), UndefinedSectionType) == UndefinedSectionType); - REQUIRE(ClassifyBlock(cur, markdown.end(), ParametersSectionType) == ParameterDefinitionSectionType); + REQUIRE(!markdownAST.children().empty()); + REQUIRE(SectionProcessor::sectionType(markdownAST.children().begin()) == ParametersSectionType); } TEST_CASE("Parse canonical parameters", "[parameters]") { - MarkdownBlock::Stack markdown = CanonicalParametersFixture(); - ParameterCollection parameters; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParametersParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameters); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - REQUIRE(std::distance(blocks.begin(), result.second) == 41); - - REQUIRE(parameters.size() == 2); + Parameters parameters; + Report report; + SectionParserHelper::parse(ParametersFixture, ParametersSectionType, report, parameters); - REQUIRE(parameters[0].name == "id"); - REQUIRE(parameters[0].description == "2"); - - REQUIRE(parameters[1].name == "limit"); - REQUIRE(parameters[1].description == "2"); -} + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.empty()); -TEST_CASE("Parse description parameter only", "[parameters]") -{ - //+ Parameters - // - // + param1 - // - // A - - MarkdownBlock::Stack markdown; - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Parameters", 0, MakeSourceDataBlock(0, 1))); - - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Param1", 0, MakeSourceDataBlock(1, 1))); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "A", 0, MakeSourceDataBlock(2, 1))); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(3, 1))); - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0, MakeSourceDataBlock(4, 1))); - - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(5, 1))); - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0, MakeSourceDataBlock(6, 1))); - - - ParameterCollection parameters; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParametersParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameters); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - REQUIRE(std::distance(blocks.begin(), result.second) == 11); - - REQUIRE(parameters.size() == 1); - - REQUIRE(parameters[0].name == "Param1"); - REQUIRE(parameters[0].description == "2"); - REQUIRE(parameters[0].use == UndefinedParameterUse); - REQUIRE(parameters[0].type.empty()); - REQUIRE(parameters[0].defaultValue.empty()); - REQUIRE(parameters[0].exampleValue.empty()); - REQUIRE(parameters[0].values.empty()); -} - -TEST_CASE("Parse multiple parameters", "[parameters]") -{ - //+ Parameters - // - // + param1 (Optional) - // - // A - // - // - // + param2 (`B-2`) - // - // B - // - // + param3 - - MarkdownBlock::Stack markdown; - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Parameters", 0, MakeSourceDataBlock(0, 1))); + REQUIRE(parameters.size() == 2); - markdown.push_back(MarkdownBlock(ListBlockBeginType, SourceData(), 0, SourceDataBlock())); - - // Param 1 - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Param1 (optional)", 0, MakeSourceDataBlock(1, 1))); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "A", 0, MakeSourceDataBlock(2, 1))); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(5, 1))); - - // Param 2 - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Param2 (`B-2`)", 0, MakeSourceDataBlock(6, 1))); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "B", 0, MakeSourceDataBlock(7, 1))); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(10, 1))); - - // Param 3 - markdown.push_back(MarkdownBlock(ListItemBlockBeginType, SourceData(), 0, SourceDataBlock())); - markdown.push_back(MarkdownBlock(ParagraphBlockType, "Param3", 0, MakeSourceDataBlock(11, 1))); - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(12, 1))); - - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0, MakeSourceDataBlock(13, 1))); - - markdown.push_back(MarkdownBlock(ListItemBlockEndType, SourceData(), 0, MakeSourceDataBlock(14, 1))); - markdown.push_back(MarkdownBlock(ListBlockEndType, SourceData(), 0, MakeSourceDataBlock(15, 1))); + REQUIRE(parameters[0].name == "id"); + REQUIRE(parameters[0].description == "Lorem ipsum\n"); - ParameterCollection parameters; - BlueprintParserCore parser(0, SourceDataFixture, Blueprint()); - BlueprintSection rootSection(std::make_pair(markdown.begin(), markdown.end())); - ParseSectionResult result = ParametersParser::Parse(markdown.begin(), markdown.end(), rootSection, parser, parameters); - - REQUIRE(result.first.error.code == Error::OK); - REQUIRE(result.first.warnings.empty()); - - const MarkdownBlock::Stack &blocks = markdown; - REQUIRE(std::distance(blocks.begin(), result.second) == 18); - - REQUIRE(parameters.size() == 3); - - REQUIRE(parameters[0].name == "Param1"); - REQUIRE(parameters[0].description == "2"); - REQUIRE(parameters[0].use == OptionalParameterUse); - REQUIRE(parameters[0].type.empty()); - REQUIRE(parameters[0].defaultValue.empty()); - REQUIRE(parameters[0].exampleValue.empty()); - REQUIRE(parameters[0].values.empty()); - - REQUIRE(parameters[1].name == "Param2"); - REQUIRE(parameters[1].description == "7"); - REQUIRE(parameters[1].use == UndefinedParameterUse); - REQUIRE(parameters[1].type.empty()); - REQUIRE(parameters[1].defaultValue.empty()); - REQUIRE(parameters[1].exampleValue == "B-2"); - REQUIRE(parameters[1].values.empty()); - - REQUIRE(parameters[2].name == "Param3"); - REQUIRE(parameters[2].description.empty()); - REQUIRE(parameters[2].use == UndefinedParameterUse); - REQUIRE(parameters[2].type.empty()); - REQUIRE(parameters[2].defaultValue.empty()); - REQUIRE(parameters[2].exampleValue.empty()); - REQUIRE(parameters[2].values.empty()); + REQUIRE(parameters[1].name == "name"); + REQUIRE(parameters[1].description.empty()); } TEST_CASE("Parse ilegal parameter", "[parameters]") { - // Blueprint in question: - //R"( - //# /1 - //+ Parameters - // + i:legal - //"); - const std::string blueprintSource = \ - "# /1\n"\ + mdp::ByteBuffer source = \ "+ Parameters\n"\ " + i:legal\n\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 2); - REQUIRE(result.warnings[0].code == IgnoringWarning); - REQUIRE(result.warnings[1].code == FormattingWarning); // no parameters specified - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.empty()); + + Parameters parameters; + Report report; + SectionParserHelper::parse(source, ParametersSectionType, report, parameters); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == IgnoringWarning); + + REQUIRE(parameters.empty()); } -TEST_CASE("Parse ilegal parameter among legal ones", "[parameters]") +TEST_CASE("Parse illegal parameter among legal ones", "[parameters]") { - // Blueprint in question: - //R"( - //# /1/{OK-1}/{OK-2} - //+ Parameters - // + OK-1 - // + i:legal - // + OK-2 - //"); - const std::string blueprintSource = \ - "# /1/{OK-1}/{OK-2}\n"\ + mdp::ByteBuffer source = \ "+ Parameters\n"\ " + OK-1\n"\ " + i:legal\n"\ - " + OK-2\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 2); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "OK-1"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[1].name == "OK-2"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[1].description.empty()); + " + OK-2\n"; + + Parameters parameters; + Report report; + SectionParserHelper::parse(source, ParametersSectionType, report, parameters); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == IgnoringWarning); + + REQUIRE(parameters.size() == 2); + REQUIRE(parameters[0].name == "OK-1"); + REQUIRE(parameters[0].description.empty()); + REQUIRE(parameters[1].name == "OK-2"); + REQUIRE(parameters[1].description.empty()); } TEST_CASE("Warn about additional content in parameters section", "[parameters]") { - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // extra-1 - // - // + id - // - //+ Response 204 - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ + mdp::ByteBuffer source = \ "+ Parameters\n"\ - " extra-1\n"\ - "\n"\ + " extra-1\n\n"\ " + id\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].description.empty()); -} + Parameters parameters; + Report report; + SectionParserHelper::parse(source, ParametersSectionType, report, parameters); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == IgnoringWarning); + + REQUIRE(parameters.size() == 1); + REQUIRE(parameters[0].name == "id"); + REQUIRE(parameters[0].description.empty()); +} TEST_CASE("Warn about additional content block in parameters section", "[parameters]") { - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // - // extra-1 - // - // + id - // - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ - "+ Parameters\n"\ - "\n"\ - " extra-1\n"\ - "\n"\ - " + id\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == IgnoringWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].description.empty()); + mdp::ByteBuffer source = \ + "+ Parameters\n\n"\ + " extra-1\n\n"\ + " + id\n"; + + Parameters parameters; + Report report; + SectionParserHelper::parse(source, ParametersSectionType, report, parameters); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == IgnoringWarning); + + REQUIRE(parameters.size() == 1); + REQUIRE(parameters[0].name == "id"); + REQUIRE(parameters[0].description.empty()); } TEST_CASE("Warn about multiple parameters with the same name", "[parameters]") { - // Blueprint in question: - //R"( - //# /1/{id} - //+ Parameters - // + id (`42`) - // + id (`43`) - //"); - const std::string blueprintSource = \ - "# /1/{id}\n"\ + mdp::ByteBuffer source = \ "+ Parameters\n"\ " + id (`42`)\n"\ - " + id (`43`)\n"\ - "\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.size() == 1); - REQUIRE(result.warnings[0].code == RedefinitionWarning); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].parameters[0].exampleValue == "43"); -} + " + id (`43`)\n"; -TEST_CASE("Parse parameters with dot in its name", "[parameters][issue][#47][source]") -{ - // Blueprint in question: - //R"( - //# GET /contracts?product.id=4 - // - //+ Parameters - // + product.id ... Hello - // - //+ Response 204 - //"); - const std::string blueprintSource = \ - "# GET /contracts?product.id=4\n"\ - "\n"\ - "+ Parameters\n"\ - " + product.id ... Hello\n"\ - "\n"\ - "+ Response 204\n\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.empty()); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].name == "product.id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].description == "Hello"); -} + Parameters parameters; + Report report; + SectionParserHelper::parse(source, ParametersSectionType, report, parameters); -TEST_CASE("Parentheses in parameter description ", "[parameters][issue][#49][source]") -{ - // Blueprint in question: - //R"( - //# GET /{id} - //+ Parameters - // + id (string) ... lorem (ipsum) - // - //+ response 204 - //"); - const std::string blueprintSource = \ - "# GET /{id}\n"\ - "+ Parameters\n"\ - " + id (string) ... lorem (ipsum)\n"\ - "\n"\ - "+ response 204\n"; - - Parser parser; - Result result; - Blueprint blueprint; - parser.parse(blueprintSource, 0, result, blueprint); - REQUIRE(result.error.code == Error::OK); - REQUIRE(result.warnings.empty()); - - REQUIRE(blueprint.resourceGroups.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].description.empty()); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters.size() == 1); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].name == "id"); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].type == "string"); - REQUIRE(blueprint.resourceGroups[0].resources[0].actions[0].parameters[0].description == "lorem (ipsum)"); + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == RedefinitionWarning); + + REQUIRE(parameters.size() == 1); + REQUIRE(parameters[0].name == "id"); + REQUIRE(parameters[0].exampleValue == "43"); } diff --git a/test/test-ValuesParser.cc b/test/test-ValuesParser.cc new file mode 100644 index 00000000..5e9038e2 --- /dev/null +++ b/test/test-ValuesParser.cc @@ -0,0 +1,84 @@ +// +// test-ValuesParser.cc +// snowcrash +// +// Created by Pavan Kumar Sunkara on 6/12/14. +// Copyright (c) 2014 Apiary Inc. All rights reserved. +// + +#include "snowcrashtest.h" +#include "ValuesParser.h" + +using namespace snowcrash; +using namespace snowcrashtest; + +const mdp::ByteBuffer ValuesFixture = \ +"+ Values\n"\ +" + `1234`\n"\ +" + `0000`\n"\ +" + `beef`\n"\ +""; + +TEST_CASE("Recognize values signature", "[values]") +{ + mdp::MarkdownParser markdownParser; + mdp::MarkdownNode markdownAST; + markdownParser.parse(ValuesFixture, markdownAST); + + REQUIRE(!markdownAST.children().empty()); + REQUIRE(SectionProcessor::sectionType(markdownAST.children().begin()) == ValuesSectionType); +} + +TEST_CASE("Parse canonical values", "[values]") +{ + Values values; + Report report; + SectionParserHelper::parse(ValuesFixture, ValuesSectionType, report, values); + + REQUIRE(report.error.code == Error::OK); + CHECK(report.warnings.empty()); + + REQUIRE(values.size() == 3); + REQUIRE(values[0] == "1234"); + REQUIRE(values[1] == "0000"); + REQUIRE(values[2] == "beef"); +} + +TEST_CASE("Warn superfluous content in values attribute", "[values]") +{ + mdp::ByteBuffer source = \ + "+ Values\n\n"\ + " extra\n\n"\ + " + `Hello`\n"; + + Values values; + Report report; + SectionParserHelper::parse(source, ValuesSectionType, report, values); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + REQUIRE(report.warnings[0].code == IgnoringWarning); + + REQUIRE(values.size() == 1); + REQUIRE(values[0] == "Hello"); +} + +TEST_CASE("Warn about illegal entities in values attribute", "[values]") +{ + const std::string source = \ + "+ Values\n"\ + " + `Hello`\n"\ + " + illegal\n"\ + " + `Hi`\n"; + + Values values; + Report report; + SectionParserHelper::parse(source, ValuesSectionType, report, values); + + REQUIRE(report.error.code == Error::OK); + REQUIRE(report.warnings.size() == 1); + + REQUIRE(values.size() == 2); + REQUIRE(values[0] == "Hello"); + REQUIRE(values[1] == "Hi"); +}