diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 6164a62..e4bd397 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -63,3 +63,17 @@ func GoName(concept concepts.Annotated) string { } return fmt.Sprintf("%s", name) } + +// Reference checks if the given concept has a `reference` annotation. If it has it then it returns the value +// of the `path` parameter. It returns an empty string if there is no such annotation or parameter. +func ReferencePath(concept concepts.Annotated) string { + annotation := concept.GetAnnotation("ref") + if annotation == nil { + return "" + } + name := annotation.FindParameter("path") + if name == nil { + return "" + } + return fmt.Sprintf("%s", name) +} diff --git a/pkg/concepts/type.go b/pkg/concepts/type.go index e54dee6..f155455 100644 --- a/pkg/concepts/type.go +++ b/pkg/concepts/type.go @@ -17,6 +17,7 @@ limitations under the License. package concepts import ( + "log" "sort" "github.com/openshift-online/ocm-api-metamodel/pkg/names" @@ -185,6 +186,19 @@ func (t *Type) AddAttribute(attribute *Attribute) { } } +// Remove attribute removes an attribute with a given name. +func (t *Type) RemoveAttribute(name *names.Name) { + if name == nil { + return + } + for i, attribute := range t.attributes { + if attribute.Name().Equals(name) { + log.Printf("---------- Deleting attribute %s", name.String()) + t.attributes = append(t.attributes[:i], t.attributes[i+1:]...) + } + } +} + // FindAttribute returns the attribute with the given name, or nil if no such attribute exists. func (t *Type) FindAttribute(name *names.Name) *Attribute { for _, attribute := range t.attributes { diff --git a/pkg/language/checks.go b/pkg/language/checks.go index 31fc7f9..e3cbe4b 100644 --- a/pkg/language/checks.go +++ b/pkg/language/checks.go @@ -520,8 +520,8 @@ func (r *Reader) checkParameter(parameter *concepts.Parameter) { } if typ != nil && typ != parameter.Type() { r.reporter.Errorf( - "Type of default value of parameter '%s' should be '%s'", - parameter, parameter.Type(), + "Type of default value of parameter '%s' should be '%s', instead it was %s", + parameter, parameter.Type(), typ.Name().String(), ) } } diff --git a/pkg/language/reader.go b/pkg/language/reader.go index bc65209..700e23d 100644 --- a/pkg/language/reader.go +++ b/pkg/language/reader.go @@ -30,6 +30,7 @@ import ( "github.com/antlr/antlr4/runtime/Go/antlr" + "github.com/openshift-online/ocm-api-metamodel/pkg/annotations" "github.com/openshift-online/ocm-api-metamodel/pkg/concepts" "github.com/openshift-online/ocm-api-metamodel/pkg/names" "github.com/openshift-online/ocm-api-metamodel/pkg/nomenclator" @@ -428,6 +429,64 @@ func (r *Reader) ExitClassDecl(ctx *ClassDeclContext) { typ.AddAttribute(memberCtx.GetResult()) } } + + if path := annotations.ReferencePath(typ); path != "" { + if len(r.inputs) > 1 { + panic("refernced service with multiple inputs in undefined") + } + + if r.service.Versions().Len() > 1 { + panic("cannot infer which version to add reference with multiple versions") + } + + input := r.inputs[0] + currVersion := r.service.Versions()[0] + path = strings.TrimPrefix(path, "/") + components := strings.Split(path, "/") + referencedServiceName := components[0] + referencedVersion := components[1] + referencedType := components[2] + + // Create an ad-hoc reader and model for the specific referenced service. + refReader := NewReader(). + Reporter(r.reporter) + refReader.model = concepts.NewModel() + + // Initialize the indexes of undefined concepts: + refReader.undefinedTypes = make(map[string]*concepts.Type) + refReader.undefinedResources = make(map[string]*concepts.Resource) + refReader.undefinedErrors = make(map[string]*concepts.Error) + + // load the ad-hoc service and version referenced and find the correct type. + refReader.loadService(fmt.Sprintf("%s/%s", input, referencedServiceName)) + refVersion := refReader.service.FindVersion(names.ParseUsingSeparator(referencedVersion, "_")) + // Once loading the service, we find the reference type + // then recursively iterate the type tree and add the types to the current version. + for _, currType := range refVersion.Types() { + if strings.Compare(currType.Name().String(), referencedType) == 0 { + r.recursivelyAddTypeToVersion(currVersion, currType) + } + } + } +} + +// A helper function to recursively add types to a version +func (r *Reader) recursivelyAddTypeToVersion(version *concepts.Version, typ *concepts.Type) { + var attributesToRemove concepts.AttributeSlice + for _, attribute := range typ.Attributes() { + // We wish to define links explicitly and not inherint them + // only the attribute fields. + if version.FindType(attribute.Type().Name()) == nil && !attribute.Link() { + r.recursivelyAddTypeToVersion(version, attribute.Type()) + } + if attribute.Link() { + attributesToRemove = append(attributesToRemove, attribute) + } + } + for _, attribute := range attributesToRemove { + typ.RemoveAttribute(attribute.Name()) + } + version.AddType(typ) } func (r *Reader) ExitStructDecl(ctx *StructDeclContext) { @@ -737,6 +796,67 @@ func (r *Reader) ExitLocatorDecl(ctx *LocatorDeclContext) { // Add the annotations: r.addAnnotations(locator, ctx.GetAnnotations()) + if path := annotations.ReferencePath(locator); path != "" { + if len(r.inputs) > 1 { + panic("refernced service with multiple inputs in undefined") + } + + if r.service.Versions().Len() > 1 { + panic("cannot infer which version to add reference with multiple versions") + } + + input := r.inputs[0] + currVersion := r.service.Versions()[0] + path = strings.TrimPrefix(path, "/") + components := strings.Split(path, "/") + referencedServiceName := components[0] + referencedVersion := components[1] + referencedLocator := components[2] + + // Create an ad-hoc reader and model for the specific referenced service. + refReader := NewReader(). + Reporter(r.reporter) + refReader.model = concepts.NewModel() + + // Initialize the indexes of undefined concepts: + refReader.undefinedTypes = make(map[string]*concepts.Type) + refReader.undefinedResources = make(map[string]*concepts.Resource) + refReader.undefinedErrors = make(map[string]*concepts.Error) + + // load the ad-hoc service and version referenced and find the correct locator. + refReader.loadService(fmt.Sprintf("%s/%s", input, referencedServiceName)) + refVersion := refReader.service.FindVersion(names.ParseUsingSeparator(referencedVersion, "_")) + for _, currLocator := range refVersion.Root().Locators() { + if strings.Compare(currLocator.Name().String(), referencedLocator) == 0 { + for _, resource := range refVersion.Resources() { + if resource.IsRoot() { + // We do not need to copy over the root resource. + continue + } + + // Add any underlying resource to the current version. + currVersion.AddResource(resource) + // Add underlying methods. + for _, method := range resource.Methods() { + currVersion. + FindResource(resource.Name()). + AddMethod(method) + } + + // Remove any undefined resources as + // This is a no-op except for the reference target added + // by the locator as the compiler will attempt to compile it + r.removeUndefinedResource(resource) + } + + currVersion.AddTypes(refVersion.Types()) + locator = currLocator + ctx.SetResult(locator) + return + } + } + } + // Add the members: membersCtxs := ctx.GetMembers() if len(membersCtxs) > 0 {