Skip to content

Commit

Permalink
[WIP] API parsing support
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-palyanitsa committed Nov 11, 2024
1 parent 7725750 commit b731d83
Show file tree
Hide file tree
Showing 26 changed files with 8,237 additions and 6,291 deletions.
107 changes: 56 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,62 +140,67 @@ and are not limited to the mentioned use cases.
The following sections are currently implemented. See notes for each point:

- [ ] RAML API definitions
- [ ] Resource Types
- [ ] Traits
- [x] Endpoint definitions
- [ ] Security schemes
- [x] Documentation
- [x] RAML Data Types
- [x] Defining Types
- [x] Type Declarations
- [x] Built-in Types
- [x] The "Any" Type
- [x] Object Type
- [x] Property Declarations (explicit and pattern properties)
- [x] Additional Properties
- [x] Object Type Specialization
- [x] Using Discriminator
- [x] Array Type
- [x] Scalar Types
- [x] String
- [x] Number
- [x] Integer
- [x] Boolean
- [x] Date
- [x] File
- [x] Nil Type
- [x] Union Type (mostly supported, lacks enum support)
- [x] JSON Schema types (supported, but validation is not implemented)
- [x] Recursive types
- [x] User-defined Facets
- [x] Determine Default Types
- [x] Type Expressions
- [x] Type Inheritance
- [ ] Multiple Inheritance (supported, but not fully compliant)
- [x] Inline Type Declarations
- [x] Defining Examples in RAML
- [x] Multiple Examples
- [x] Single Example
- [x] Validation against defined data type
- [x] Defining Types
- [x] Type Declarations
- [x] Built-in Types
- [x] The "Any" Type
- [x] Object Type
- [x] Property Declarations (explicit and pattern properties)
- [x] Additional Properties
- [x] Object Type Specialization
- [x] Using Discriminator
- [x] Array Type
- [x] Scalar Types
- [x] String
- [x] Number
- [x] Integer
- [x] Boolean
- [x] Date
- [x] File
- [x] Nil Type
- [x] Union Type (mostly supported, lacks enum support)
- [ ] JSON Schema types (supported, but validation is not implemented)
- [x] Recursive types
- [x] User-defined Facets
- [x] Determine Default Types
- [x] Type Expressions
- [x] Type Inheritance
- [ ] Multiple Inheritance (supported, but not fully compliant)
- [x] Inline Type Declarations
- [x] Defining Examples in RAML
- [x] Multiple Examples
- [x] Single Example
- [x] Validation against defined data type
- [ ] Annotations
- [x] Declaring Annotation Types
- [ ] Applying Annotations
- [ ] Annotating Scalar-valued Nodes
- [ ] Annotation Targets
- [x] Annotating types
- [x] Declaring Annotation Types
- [ ] Applying Annotations
- [ ] Annotating Scalar-valued Nodes
- [ ] Annotation Targets
- [x] Annotating types
- [ ] Modularization
- [ ] Includes
- [x] Library
- [x] NamedExample
- [x] DataType
- [ ] AnnotationTypeDeclaration
- [ ] DocumentationItem
- [ ] ResourceType
- [ ] Trait
- [ ] Overlay
- [ ] Extension
- [ ] SecurityScheme
- [ ] Includes
- [x] Library
- [x] NamedExample
- [x] DataType
- [x] AnnotationTypeDeclaration
- [x] DocumentationItem
- [x] ResourceType
- [x] Trait
- [ ] Overlay
- [ ] Extension
- [x] SecurityScheme
- [ ] Conversion
- [x] Conversion to JSON Schema
- [ ] Conversion to RAML
- [x] Conversion to JSON Schema
- [ ] Conversion to RAML
- [ ] CLI
- [x] Validate
- [ ] Convert to JSON Schema
- [x] Validate RAML
- [ ] Convert RAML data type to JSON Schema

## Comparison to existing libraries

Expand Down
18 changes: 18 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
package raml

import (
"regexp"

"github.com/acronis/go-stacktrace"
"gopkg.in/yaml.v3"
)

type Value[T any] struct {
Value T
stacktrace.Position
Location string
}

func (v *Value[T]) decode(node *yaml.Node, location string) error {
v.Location = location
v.Position = stacktrace.Position{Line: node.Line, Column: node.Column}
if node.Tag == "!include" {
return nil
}
if err := node.Decode(&v.Value); err != nil {
return StacktraceNewWrapped("decode value", err, v.Location, WithNodePosition(node))
}
return nil
}

var ErrNil error

var TemplateVariableRe = regexp.MustCompile(`<<\s*(\w+)\s*(\|\s*(![a-z]+))?\s*>>`)
45 changes: 31 additions & 14 deletions complex.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"

orderedmap "github.com/wk8/go-ordered-map/v2"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"

"github.com/acronis/go-stacktrace"
Expand Down Expand Up @@ -641,19 +642,20 @@ func (s *ObjectShape) checkProperties() error {
stacktrace.WithInfo("discriminator", *s.Discriminator))
}
if !prop.Base.IsScalar() {
return StacktraceNew("discriminator property must be a scalar", s.Location,
return StacktraceNew("discriminator property type must be a scalar", s.Location,
stacktrace.WithPosition(&prop.Base.Position),
stacktrace.WithInfo("discriminator", *s.Discriminator))
}
discriminatorValue := s.DiscriminatorValue
if discriminatorValue == nil {
discriminatorValue = s.Base().Name
}
if err := prop.Base.Validate(discriminatorValue); err != nil {
return StacktraceNewWrapped("validate discriminator value", err, s.Location,
stacktrace.WithPosition(&s.Base().Position),
stacktrace.WithInfo("discriminator", *s.Discriminator))
}
// FIXME: Discriminator value must not be validated against base type.
// discriminatorValue := s.DiscriminatorValue
// if discriminatorValue == nil {
// discriminatorValue = s.Base().Name
// }
// if err := prop.Base.Validate(discriminatorValue); err != nil {
// return StacktraceNewWrapped("validate discriminator value", err, s.Location,
// stacktrace.WithPosition(&s.Base().Position),
// stacktrace.WithInfo("discriminator", *s.Discriminator))
// }
}

return nil
Expand Down Expand Up @@ -888,8 +890,9 @@ type JSONShape struct {
noScalarShape
*BaseShape

Schema *JSONSchema
Raw string
Schema any
Validator *gojsonschema.Schema
Raw string
}

func (s *JSONShape) Base() *BaseShape {
Expand All @@ -908,8 +911,21 @@ func (s *JSONShape) clone(base *BaseShape, _ map[int64]*BaseShape) Shape {
return &c
}

func (s *JSONShape) validate(_ interface{}, _ string) error {
// TODO: Implement validation with JSON Schema
func (s *JSONShape) validate(v interface{}, _ string) error {
l := gojsonschema.NewGoLoader(v)

result, err := s.Validator.Validate(l)
if err != nil {
return StacktraceNewWrapped("validate JSON schema", err, s.Location, stacktrace.WithPosition(&s.Position))
}

if !result.Valid() {
st := StacktraceNew("failed to validate against JSON schema", s.Location, stacktrace.WithPosition(&s.Position))
for _, err := range result.Errors() {
st = st.Append(StacktraceNew(err.String(), s.Location, stacktrace.WithPosition(&s.Position)))
}
return st
}
return nil
}

Expand All @@ -931,6 +947,7 @@ func (s *JSONShape) inherit(source Shape) (Shape, error) {
}
s.Schema = ss.Schema
s.Raw = ss.Raw
s.Validator = ss.Validator
return s, nil
}

Expand Down
48 changes: 48 additions & 0 deletions consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const (
FacetUniqueItems = "uniqueItems"
FacetDiscriminator = "discriminator"
FacetDiscriminatorValue = "discriminatorValue"
FacetValue = "value"
FacetDescription = "description"
FacetDisplayName = "displayName"
FacetStrict = "strict"
Expand All @@ -89,6 +90,20 @@ const (
FacetExamples = "examples"
FacetDefault = "default"
FacetAllowedTargets = "allowedTargets"
FacetHeaders = "headers"
FacetQueryParameters = "queryParameters"
FacetQueryString = "queryString"
FacetResponses = "responses"
FacetBody = "body"
FacetAnnotations = "annotations"
FacetProtocols = "protocols"
FacetSchemes = "schemes"
FacetSecuredBy = "securedBy"
FacetIs = "is"
FacetTraits = "traits"
FacetResourceTypes = "resourceTypes"
FacetUses = "uses"
FacetUriParameters = "uriParameters"

Check failure on line 106 in consts.go

View workflow job for this annotation

GitHub Actions / build

var-naming: const FacetUriParameters should be FacetURIParameters (revive)
)

const (
Expand All @@ -110,3 +125,36 @@ const (
StacktraceTypeReading stacktrace.Type = "reading"
StacktraceTypeLoading stacktrace.Type = "loading"
)

type SecuritySchemeType string

const (
BasicAuthType SecuritySchemeType = "Basic Authentication"
DigestAuthType SecuritySchemeType = "Digest Authentication"
PassThroughAuthType SecuritySchemeType = "Pass Through"
OAuth1AuthType SecuritySchemeType = "OAuth 1.0"
OAuth2AuthType SecuritySchemeType = "OAuth 2.0"
NullAuthType SecuritySchemeType = "Null"
)

type DomainLocation string

const (
APIDomain DomainLocation = "API"
DocumentationItemDomain DomainLocation = "DocumentationItem"
ResourceDomain DomainLocation = "Resource"
MethodDomain DomainLocation = "Method"
ResponseDomain DomainLocation = "Response"
RequestBodyDomain DomainLocation = "RequestBody"
ResponseBodyDomain DomainLocation = "ResponseBody"
TypeDeclarationDomain DomainLocation = "TypeDeclaration"
ExampleDomain DomainLocation = "Example"
ResourceTypeDomain DomainLocation = "ResourceType"
TraitDomain DomainLocation = "Trait"
SecuritySchemeDomain DomainLocation = "SecurityScheme"
SecuritySchemeSettingsDomain DomainLocation = "SecuritySchemeSettings"
AnnotationTypeDomain DomainLocation = "AnnotationType"
LibraryDomain DomainLocation = "Library"
OverlayDomain DomainLocation = "Overlay"
ExtensionDomain DomainLocation = "Extension"
)
83 changes: 83 additions & 0 deletions documentationitem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package raml

import (
"github.com/acronis/go-stacktrace"
orderedmap "github.com/wk8/go-ordered-map/v2"
"gopkg.in/yaml.v3"
)

type DocumentationItem struct {
ID int64

Title string
Content string

Link *DocumentationItemFragment

CustomDomainProperties *orderedmap.OrderedMap[string, *DomainExtension]

Location string
stacktrace.Position
raml *RAML
}

func (di *DocumentationItem) decode(node *yaml.Node) error {
for i := 0; i != len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
switch keyNode.Value {
case "title":
if err := valueNode.Decode(&di.Title); err != nil {
return StacktraceNewWrapped("decode title", err, di.Location, WithNodePosition(valueNode))
}
case "content":
if err := valueNode.Decode(&di.Content); err != nil {
return StacktraceNewWrapped("decode content", err, di.Location, WithNodePosition(valueNode))
}
default:
if IsCustomDomainExtensionNode(keyNode.Value) {
name, de, err := di.raml.unmarshalCustomDomainExtension(di.Location, keyNode, valueNode)
if err != nil {
return StacktraceNewWrapped("unmarshal custom domain extension", err, di.Location, WithNodePosition(valueNode))
}
di.CustomDomainProperties.Set(name, de)
} else {
return StacktraceNew("unknown field", di.Location, stacktrace.WithInfo("field", keyNode.Value))
}
}
}
return nil
}

func (r *RAML) unmarshalDocumentationItems(node *yaml.Node, location string) ([]*DocumentationItem, error) {
if node.Kind != yaml.SequenceNode {
return nil, StacktraceNew("documentation item must be a mapping node", location, WithNodePosition(node))
}

documentationItems := make([]*DocumentationItem, len(node.Content))
for i, itemNode := range node.Content {
documentationItem, err := r.unmarshalDocumentationItem(itemNode, location)
if err != nil {
return nil, StacktraceNewWrapped("unmarshal documentation item", err, location, WithNodePosition(itemNode))
}
documentationItems[i] = documentationItem
}
return documentationItems, nil
}

func (r *RAML) unmarshalDocumentationItem(node *yaml.Node, location string) (*DocumentationItem, error) {
if node.Kind != yaml.MappingNode {
return nil, StacktraceNew("documentation item must be a mapping node", location, WithNodePosition(node))
}

documentationItem := &DocumentationItem{
CustomDomainProperties: orderedmap.New[string, *DomainExtension](0),

raml: r,
Location: location,
}
if err := documentationItem.decode(node); err != nil {
return nil, StacktraceNewWrapped("decode documentation item", err, location, WithNodePosition(node))
}
return documentationItem, nil
}
Loading

0 comments on commit b731d83

Please sign in to comment.