Canonical specifications with a declarative description of the rules for the aggregate, as well as integration with popular frameworks
- Defining your own specifications
- Combining specifications
- Description of aggregate rules
- Getting a declaration - a list of rules
- ASP.NET MVC and Swagger integration
- Getting a detailed description of the specification violation
- https://www.nuget.org/packages/ap.Specification/
- https://www.nuget.org/packages/ap.Specification.AspNet/
- https://www.nuget.org/packages/ap.Specification.Swagger/
public class FormulaMaxDepthSpec : CompositeSpecLeaf<ParseTreeNode>
{
public override bool IsSatisfiedBy(ParseTreeNode value) =>
new FormulaAnalyzer(value).OperatorDepth(ExcelFormula.AllowedNamedFunctions) <= 65;
public override SpecCondition IsSatisfiedOn => $"Depth {must_not} exceed 64";
}
must
and must_not
are special reserved words that you must use when implicitly using MustSpecConfition
or use explicit PredefinedSpecCondition
, where you explicitly negate the string literal
new NullSpec<decimal>()
.Or(new MinSpec<decimal>(0)
.And(new MaxSpec<decimal>(100))
.And(new EqualsSpec<decimal>(3).Not()))
.Not()
As a result, a tree will be formed in which we can lower not to leaves using de Morgan's law, and then apply negation to the specification, which is why we use SpecCondition
implementations instead of ordinary strings - they know how to build their negation. You will receive a specification that will comply with:
Value must not be null and (Value must inferior 0 or Value must exceed 100 or Value must be equals 3)
Replace
the specification base if it does not suit you, or if you want to bring several specifications to the same base for the purpose of their further combination:
new FormulaParsedSpec()
.And(new FormulaMaxDepthSpec())
.And(new FormulaAllowedSpec())
.And(new FormulaHasExistVariablesSpec(excelVariables))
.Replace<ParseTreeNode?, string>(ParseFormula);
In this example, we have replaced ISpec<ParseTreeNode>
with ISpec<string>
- Matrix
- SmartTasks
- Weight
- Name
- TargetResult
- SmartTasks
- Something other
Specs
.For<Matrix>()
.Nested(b => b.To(m => m.SmartTasks), Specs
.For<SmartTask>()
.Member(k => k.Weight, new MoreSpec<decimal>(0).And(new DecimalFractionMaxLengthSpec(2)))
.Member(s => s.Name, new StringNotEmptySpec()
.And(new StringMaxLengthSpec(100))
.And(new StringNotContinuousSpacesSpec())
.And(new StringNotEdgeSpaceSpec())
.And(new StringMatchSpec("\n").Not()))
.Member(s => s.TargetResult, new NullSpec<string>().Or(new StringMaxLengthSpec(200)
.And(new StringNotContinuousSpacesSpec())
.And(new StringNotEdgeSpaceSpec())
.And(new StringMatchSpec("\n").Not()))!))
.ThrowIfNotSatisfied(matrix);
For
- grab context forT
rootMe
- define a rule for the captured rootMember
- define the rule for the member of the captured rootNested
- traverse the members of the captured root up to a certain nesting levelThrowIfNotSatisfied
- throw an exception if at least one rule was violated for an entity
Specs
.For<Matrix>()
.Nested(m => m.To(ma => ma.SmartTasks), Specs
.For<SmartTask>()
.Member(k => k.Weight, new MoreSpec<decimal>(0).And(new DecimalFractionMaxLengthSpec(2)))
.Member(s => s.Name, new StringNotEmptySpec()
.And(new StringMaxLengthSpec(100))
.And(new StringNotContinuousSpacesSpec())
.And(new StringNotEdgeSpaceSpec())
.And(new StringMatchSpec("\n").Not()))
.Member(s => s.TargetResult, new NullSpec<string>().Or(new StringMaxLengthSpec(200)
.And(new StringNotContinuousSpacesSpec())
.And(new StringNotEdgeSpaceSpec())
.And(new StringMatchSpec("\n").Not()))!))
.Select(i => i.GetDeclaration())
We can do this in view of the fact that we define all the rules declaratively
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
// connecting asp.net mvc filters for errors handling
.AddSpecHandling()
.Services
.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo());
// connecing swagger models
c.AddSpecModels();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) =>
app
.UseSwagger()
.UseSwaggerUI(c => );
}
Comparison of errors and their codes (all specs built into the library are already mapped into native codes):
SpecGlobalConfig.DefaultSpecNodeMapBuilder = SpecGlobalConfig.DefaultSpecNodeMapBuilder
.StartFrom(10, b => b
.Add(typeof(FalseSpec))
.AddNot(typeof(TrueSpec))
.AddBoth(typeof(DateTimeZeroTimeSpec))
.AddForAllEmbeddedTypes(typeof(EqualsSpec<>)))
.Add(typeof(DecimalFractionMaxLengthSpec), 1)
.AddNot(typeof(StringCharactersSpec), 2)
.AddBoth(typeof(StringMatchSpec), 3, 4);
specNodeId
- error identifierisSatisfiedOn
- all the rules associated with the contextinfluenceOn
- is a broken ruleinfluenceValue
- the value that the rule did not passpath
- the path to the property inside the aggregate
- Implement the display of the declaration in the swager, so that you can see all the rules associated with the aggregate
- More built-in specs
MIT License
Copyright (c) 2021 Alexey Politov
https://github.com/EmptyBucket/FluentSpecification
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.