Skip to content

Update README #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 137 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,119 @@ This package aims to achieve the following:
1. A linter that may provide additional validations or style enforcement.
1. Conversion of parsed structure into JSON Schema and back to RAML.

## Why RAML?

RAML is a powerful modelling language that encourages design-first approach to API definitions by offering
modularity and flexibility. Combined with a powerful type system with inheritance support that it offers,
it also allows the developers to easily define complex data type schemas by leveraging
both inheritance mechanism (by referencing a parent type with common properties) and modularity (by referencing common data type fragments).

RAML uses YAML as a markup language which also contributes to readability, especially in complex definitions.

For comparison, an example of a simple data type schema in RAML would be the following:

```yaml
#%RAML 1.0 Library

types:
CommonType:
additionalProperties: false
properties:
id: string
content: object
example:
id: sample.message
content: {}

DerivedType:
type: CommonType # inherits from "CommonType"
properties:
content:
properties:
value: integer
example:
id: "metrics.message"
content:
value: 0
```

Where `DerivedType` would translate into the following JSON Schema using Draft-7 specification due to the lack
of the inheritance support:

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/DerivedType",
"definitions": {
"DerivedType": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"content": {
"type": "object",
"properties": {
"value": {
"type": "integer"
}
},
"required": ["value"]
}
},
"required": ["id", "content"]
}
}
}
```

## What you can do with it

### Creating API definitions

RAML's primary objective is to provide a specification for maintainable, design-driven API definitions.

By using RAML, you can make a well-structured API definition that would be easy to read, maintain
and use.

### Data structures validation

Given a powerful type definition system, you can declaratively define your data schema and validate
data instances against that schema.

### Defining static configuration

On top of the type definition system, RAML allows defining metadata using custom typed annotations.
One of the ways that a custom annotation may be used by processors is to create a static configuration.
that is validated against a specific schema. For example:

```yaml
#%RAML 1.0 Library

types:
# Define a data type for configuration
Config:
additionalProperties: false
properties:
host: string
port: integer

annotationTypes:
# Define a custom annotation that would be used to create an instance of a type
ConfigInstance: Config

# Create an instance of a type.
(ConfigInstance):
host: example.com
port: 8080
```

### And more

With all the features, there can be more creative cases that can be covered
and are not limited to the mentioned use cases.

## Supported features of RAML 1.0 specification

The following sections are currently implemented. See notes for each point:
Expand Down Expand Up @@ -51,7 +164,7 @@ The following sections are currently implemented. See notes for each point:
- [x] User-defined Facets
- [x] Determine Default Types
- [x] Type Expressions
- [x] Inheritance
- [x] Type Inheritance
- [ ] Multiple Inheritance (supported, but not fully compliant)
- [x] Inline Type Declarations
- [x] Defining Examples in RAML
Expand Down Expand Up @@ -134,21 +247,24 @@ make install

### Parser options

By default, parser outputs the resulting model as is. This means that information about all links and inheritance chains
By default, parser outputs the resulting model as is and without validation. This means that information about all links and inheritance chains
is unmodified. Be aware
that the parser may generate recursive structures, depending on your definition, and you may need to implement recursion
detection with the model.
detection when traversing the model.

The parser currently provides two options:

* `raml.OptWithValidate()` - performs validation of the resulting model (types inheritance validation, types facet
validations, annotation types and instances validation, examples, defaults, instances, etc.). Also performs unwrap if
`raml.OptWithUnwrap()` was not specified, but leaves the original model untouched.
`raml.OptWithUnwrap()` was not specified, but leaves the original types untouched.

* `raml.OptWithUnwrap()` - performs an unwrap of the resulting model and replaces all definitions with unwrapped
structures. Unwrap resolves the inheritance chain and links and compiles a complete type, with all properties of its
parents/links.

> [!NOTE]
> In most cases, the use of both flags is advised. If you need to access unmodified types, use only `OptWithValidate()`. Note that memory consumption may be higher and processing time may be longer since `OptWithValidate()` performs a dedicated copy and unwrap for each type.

### Parsing from string

The following code will parse a RAML string, output a library model and print the common information about the defined
Expand Down Expand Up @@ -182,25 +298,26 @@ types:
`

// Parse with validation
// Here we omit OptWithUnwrap to show the difference between child and parent.
r, err := raml.ParseFromString(content, "library.raml", workDir, raml.OptWithValidate())
if err != nil {
log.Fatal(err)
}
// Cast type to Library since our fragment is RAML 1.0 Library
lib, _ := r.EntryPoint().(*raml.Library)
typPtr, _ := lib.Types.Get("ChildType")
base, _ := lib.Types.Get("ChildType")
// Cast type to StringShape since child type inherits from a string type
typ := (*typPtr).(*raml.StringShape)
typ := base.Shape.(*raml.StringShape)
fmt.Printf(
"Type name: %s, type: %s, minLength: %d, location: %s\n",
typ.Base().Name, typ.Base().Type, *typ.MinLength, typ.Base().Location,
typ.Name, typ.Type, *typ.MinLength, typ.Location,
)
// Cast type to StringShape since parent type is string
parentTyp := (*typ.Base().Inherits[0]).(*raml.StringShape)
parentTyp := base.Inherits[0].Shape.(*raml.StringShape)
fmt.Printf("Inherits from:\n")
fmt.Printf(
"Type name: %s, type: %s, minLength: %d, location: %s\n",
parentTyp.Base().Name, parentTyp.Base().Type, parentTyp.MinLength, parentTyp.Base().Location,
parentTyp.Name, parentTyp.Type, parentTyp.MinLength, parentTyp.Location,
)
}
```
Expand All @@ -224,21 +341,20 @@ package main
import (
"fmt"
"log"
"os"

"github.com/acronis/go-raml"
)

func main() {
filePath := "<path_to_your_file>"
r, err := raml.ParseFromPath(content, "library.raml", workDir, raml.OptWithValidate())
r, err := raml.ParseFromPath(filePath, raml.OptWithValidate(), raml.OptWithUnwrap())
if err != nil {
log.Fatal(err)
}
// Assuming that the parsed fragment is a RAML Library
lib, _ := r.EntryPoint().(*raml.Library)
typPtr, _ := lib.Types.Get("BasicType")
typ := *typPtr
fmt.Printf("Type name: %s, type: %s, location: %s", typ.Base().Name, typ.Base().Type, typ.Base().Location)
base, _ := lib.Types.Get("BasicType")
fmt.Printf("Type name: %s, type: %s, location: %s", base.Name, base.Type, base.Location)
}
```

Expand Down Expand Up @@ -280,17 +396,17 @@ types:
}
// Cast type to Library since our fragment is RAML 1.0 Library
lib, _ := r.EntryPoint().(*raml.Library)
typPtr, _ := lib.Types.Get("StringType")
base, _ := lib.Types.Get("StringType")
// Cast type to StringShape since defined type is a string
typ := (*typPtr).(*raml.StringShape)
typ := base.Shape.(*raml.StringShape)
fmt.Printf(
"Type name: %s, type: %s, minLength: %d, location: %s\n",
typ.Base().Name, typ.Base().Type, *typ.MinLength, typ.Base().Location,
typ.Name, typ.Type, *typ.MinLength, typ.Location,
)
fmt.Printf("Empty string: %v\n", typ.Validate("", "$"))
fmt.Printf("Less than 5 characters: %v\n", typ.Validate("abc", "$"))
fmt.Printf("More than 5 characters: %v\n", typ.Validate("more than 5 chars", "$"))
fmt.Printf("Not a string: %v\n", typ.Validate(123, "$"))
fmt.Printf("Empty string: %v\n", base.Validate(""))
fmt.Printf("Less than 5 characters: %v\n", base.Validate("abc"))
fmt.Printf("More than 5 characters: %v\n", base.Validate("more than 5 chars"))
fmt.Printf("Not a string: %v\n", base.Validate(123))
}
```

Expand Down
Loading