SkillAgentSearch skills...

Frontier

%CSP.REST on steroids

Install / Use

/learn @rfns/Frontier
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p> <img src="https://img.shields.io/badge/Port-enabled-green.svg" height="18"> </p>

Frontier

Frontier is a REST framework made with the purpose of reducing boilerplate code and imposing a clean coding style where dispatcher methods should always return a value or throw an exception. This way the application will never use the WRITE command manually inside these kind of methods.

Why?

Have you ever found yourself dealing with repetitive tasks like mounting objects, serializing them and eventually handling multiple kind of errors? Frontier was built to boost your development by making you focus on what really matters: your application.

Features

%CSP.REST is the base class exclusive for creating RESTful applications. While Frontier uses the %CSP.REST for welcoming the requests, it overwrites how %CSP.REST transports the request to the dispatcher method in a way that you can use it even if your application is not RESTful. Here's how Frontier compares to the default %CSP.REST:

| Feature | Frontier | %CSP.REST | :------- | :------- | :---- | | Query parameters | By argument name | Using %request.Get | | Variable number of arguments | By indexed query parameter | Using %request.Get(name, index) | | Map placeholders | Supported | Parsing from %request.URL | | Routes with regular expressions | Supported when Strict="false" | Supported | | JSON serialization | Using %Dynamic instances | Writing to the device | | Error handling | Implicit | try/catch or trap mechanism with device write | | Argument instantiation | By argument class type (query/route parameters) | Manually | | Payload handling | Triggered by %Dynamic based arguments | Manually checking from %request.Content | | SQL result serialization | Using a wrapper for the SQL API | Writing to the device | | Unmarshaling | When typing an argument and setting UNMARSHAL=1 | Manually | | Marshalling | Seamlessly returning a mixed %Dynamic type | Manually | | Serving files | Using the Frontier Files API | Not supported on %CSP.REST, but %CSP.StreamServer | | Upload handling | By using the Frontier Files Upload API | Manually from %request.MimeData | Error reporting | By using the Frontier Reporter API | Manually | Authentication | Strategy-oriented (isolated) | Based on web application config / OAuth2 API | | Stream response | Implicit | Writing to the device | | Sharing common data | Using OnDataSet method and %frontier.Data object | N/A | | Property formatters | Using %frontier.PropertyFormatter | While mounting the object |

In practice

  • Automatic exception handling: Handles the exception feedback accordingly when it's thrown. Recommended to be used along with status to notify the consumer application about errors.
ClassMethod SayHello() As %String
{
  // This will return { "result": "hello" }
  return "hello"

  // or

  $$$ThrowStatus($$$ERORR($$$GeneralError, "oops"))

  // or

  return %frontier.ThrowException("oops")

  // or (this will be captured automatically)

  set value = oops
}
  • Typed argument: Set the argument type as something that extends from %Persistent and it'll be replaced with the instance resulting from an implicit %OpenId call right before the dispatcher method is invoked. This works for both: query parameters and route parameters.
ClassMethod TestGETRouteParams(class As Frontier.UnitTest.Fixtures.Class) As %Status
{
  // curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/route-params/6'
  // {"Plate":"O5397","Students":[{"Name":"Drabek,Peter T.","__id__":"20"}],"__id__":"6"}
  return class
}
  • Query parameters: Unhandled arguments (not explicitly set on Route/Map) are considered as query parameters. By default they are required, however it's possible to make them optional simply by providing a default value.
ClassMethod SayHelloTo(who As %String = "John Doe") As %String
{
  // curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/query-params?who=Francis'
  // curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/query-params'
  return "hello "_who
}
  • Variable number of arguments: Multiple query parameters with the same name but different indexes. To enable it, use the three dot notation.
ClassMethod TestGETRestParametersSum(n... As %String) As %Integer
{
  // curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/rest-params?n1=10&n2=20&n3=30'
  // {"result":60}
  set sum = 0
  for i=1:1:n  set sum = sum + n(i)
  return sum
}
  • Payload handling: Client applications requiring to send payload data (normally JSON), can be handled by the server with methods whose parameters are typed from %DynamicAbstractObject instances.
ClassMethod EchoUserPayload(payload As %DynamicObject) As %DynamicObject
{
  // curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' 'http://localhost:57772/api/frontier/test/payload/single-object'
  // {"username":"xyz","password":"xyz"}
  return payload
}
  • Unmarshaling: Set UNMARSHAL=1 while typing the payload to a persistent class so that the payload will be parsed and unmarshalled to it.
ClassMethod CreateClass(class As Frontier.UnitTest.Fixtures.Class(UNMARSHAL=1)) As Frontier.UnitTest.Fixtures.Student
{
  // curl -H "Content-Type: application/json" -X POST\
  // -d '{"Plate": "R-2948","Students": [{"Name": "Rubens",\
  // "BirthDate": "04/21/1970","SomeValue": 0}]}'\
  // 'http://localhost:57772/api/frontier/unmarshal
  $$$ThrowOnError(class.%Save())
  return {
    "ok": true,
    "__id__": (class.%Id())
  }
}
  • SQL results: Return a serializable SQL result using the Frontier SQL API.
ClassMethod GetPaginatedSQLResult(
  page As %Integer = 1,
  rows As %Integer = 5) As Frontier.SQL.Provider
{
  set offset = (page * rows) - (rows - 1)
  set limit = page * rows

  return %frontier.SQL.Prepare(
    "SELECT *, %VID as Index FROM (SELECT TOP ? * FROM FRONTIER_UNITTEST_FIXTURES.STUDENT) WHERE %VID BETWEEN ? AND ?"
  ).Parameters(limit, offset, limit)

  // or

  return %frontier.SQL.Prepare("Package.Class:QueryName").Parameters(limit, offset)
}
  • Streams: Return a stream instance to deliver a content that exceeds the maximum string length.
ClassMethod TestGETStream() As %Stream.Object
{
  set stream = ##class(%Stream.GlobalCharacter).%New()
  do stream.Write("This line is from a stream.")

  return stream
}
  • Seamless object serialization: Mix multiple object types into a single returning %DynamicObject/%DynamicArray instance and all its composition will be mutated to dynamic instances as well.
ClassMethod TestGETMixedDynamicObject(class As Frontier.UnitTest.Fixtures.Class) As %DynamicObject
{
  // Class is a instance of a %Persistent derived type.
  return {
    "class": (class)
  }
}
  • Shareable object: Allows the context to share a set of objects that can be retrived on each dispatcher method.
ClassMethod OnDataSet(data As %DynamicObject) As %Status
{
  /// This 'data' object is shared between all methods. Accessible using %frontier.Data.
  set data.Message = "This 'Message' is shared between all methods."
  return $$$OK
}

ClassMethod TestGETData() As %DynamicObject
{
  // Prints { "results": "This 'Message' is shared for all methods." }.
  return %frontier.Data
}

Configuring the router

Each Router class provides a configuration method called OnSetup(). This method allows the developer to define how the Router should behave for certain situations. Configuration is made by using the helpers provided in the %frontier object which is composed by several modules to handle different tasks.

Authentication

The helper AuthenticationManager allows the declaration of a chain of strategies that attempt to match themselves with the authentication model and the credentials provided by the client. In other words, a strategy is elected from the chain and used to validate the credentials.

Below is an example on how to configure a strategy in the chain.

Implementation:

ClassMethod OnSetup() As %Status
{
  // Asks the user for a Basic + Base64(username:password) encoded Authorization header.
  set basicStrategy = ##class(Frontier.Authentication.BasicStrategy).%New({
    "realm": "tests",
    "validator": ($classname()_":ValidateCredentials")
  })

  // Tells Frontier that we should use this strategy.
  $$$QuitOnError(%frontier.AuthenticationManager.AddStrategy(basicStrategy))
}

NOTE: This will make all the routes under the current Router to be protected. If you want to disable authentication you can set UseAuth="false" in the related <Route>. You can also force a route to use a single strategy by using AuthStrategy="MyAuth".

Implementing a strategy

If you want to use your own strategy, you need to consider a few entry points:

  • Realm (String): This should be used to define the 'realm' when sending the challenge to the client.
  • GetChallenge (Method): This should return a valid WWW-Authenticate whenever adequate.
  • Verify (Method): This method takes four arguments: session, request, response, and user. The implementation use the three first arguments to resolve the user which is represented by a %DynamicObject. When user is populated with a scope property, then this scope will be used to validate against the Route's Scope attribute if present.

To have a better idea on how to implement a custom strategy check out the class Frontier.Authentication.BasicStrategy. It implements all the entry points described here.

Reporters

Reporters can be used to take an informative action on unhandled errors. just like the AuthenticationManager, the ReporterManager can have a ch

View on GitHub
GitHub Stars5
CategoryDevelopment
Updated1y ago
Forks1

Languages

ObjectScript

Security Score

75/100

Audited on Jul 15, 2024

No findings